Merge pull request #224 from jrperritt/v0.2.0-consistency-checks

V0.2.0 consistency checks
diff --git a/acceptance/openstack/compute/v2/flavors_test.go b/acceptance/openstack/compute/v2/flavors_test.go
index ca810bc..9c8e322 100644
--- a/acceptance/openstack/compute/v2/flavors_test.go
+++ b/acceptance/openstack/compute/v2/flavors_test.go
@@ -17,7 +17,7 @@
 
 	t.Logf("ID\tRegion\tName\tStatus\tCreated")
 
-	pager := flavors.List(client, flavors.ListFilterOptions{})
+	pager := flavors.List(client, nil)
 	count, pages := 0, 0
 	pager.EachPage(func(page pagination.Page) (bool, error) {
 		t.Logf("---")
diff --git a/acceptance/openstack/compute/v2/images_test.go b/acceptance/openstack/compute/v2/images_test.go
index 7fca3ec..6166fc8 100644
--- a/acceptance/openstack/compute/v2/images_test.go
+++ b/acceptance/openstack/compute/v2/images_test.go
@@ -17,7 +17,7 @@
 
 	t.Logf("ID\tRegion\tName\tStatus\tCreated")
 
-	pager := images.List(client)
+	pager := images.ListDetail(client, nil)
 	count, pages := 0, 0
 	pager.EachPage(func(page pagination.Page) (bool, error) {
 		pages++
diff --git a/endpoint_search_test.go b/endpoint_search_test.go
new file mode 100644
index 0000000..3457453
--- /dev/null
+++ b/endpoint_search_test.go
@@ -0,0 +1,19 @@
+package gophercloud
+
+import (
+	"testing"
+
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestApplyDefaultsToEndpointOpts(t *testing.T) {
+	eo := EndpointOpts{Availability: AvailabilityPublic}
+	eo.ApplyDefaults("compute")
+	expected := EndpointOpts{Availability: AvailabilityPublic, Type: "compute"}
+	th.CheckDeepEquals(t, expected, eo)
+
+	eo = EndpointOpts{Type: "compute"}
+	eo.ApplyDefaults("object-store")
+	expected = EndpointOpts{Availability: AvailabilityPublic, Type: "compute"}
+	th.CheckDeepEquals(t, expected, eo)
+}
diff --git a/openstack/blockstorage/v1/snapshots/requests.go b/openstack/blockstorage/v1/snapshots/requests.go
index f3180d7..7fac925 100644
--- a/openstack/blockstorage/v1/snapshots/requests.go
+++ b/openstack/blockstorage/v1/snapshots/requests.go
@@ -1,50 +1,74 @@
 package snapshots
 
 import (
+	"fmt"
+
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
 
 	"github.com/racker/perigee"
 )
 
+// CreateOptsBuilder allows extensions to add additional parameters to the
+// Create request.
+type CreateOptsBuilder interface {
+	ToSnapshotCreateMap() (map[string]interface{}, error)
+}
+
 // CreateOpts contains options for creating a Snapshot. This object is passed to
 // the snapshots.Create function. For more information about these parameters,
 // see the Snapshot object.
 type CreateOpts struct {
-	Description string                 // OPTIONAL
-	Force       bool                   // OPTIONAL
-	Metadata    map[string]interface{} // OPTIONAL
-	Name        string                 // OPTIONAL
-	VolumeID    string                 // REQUIRED
+	// OPTIONAL
+	Description string
+	// OPTIONAL
+	Force bool
+	// OPTIONAL
+	Metadata map[string]interface{}
+	// OPTIONAL
+	Name string
+	// REQUIRED
+	VolumeID string
 }
 
-// Create will create a new Snapshot based on the values in CreateOpts. To extract
-// the Snapshot object from the response, call the Extract method on the
+// ToSnapshotCreateMap assembles a request body based on the contents of a
+// CreateOpts.
+func (opts CreateOpts) ToSnapshotCreateMap() (map[string]interface{}, error) {
+	s := make(map[string]interface{})
+
+	if opts.VolumeID == "" {
+		return nil, fmt.Errorf("Required CreateOpts field 'VolumeID' not set.")
+	}
+	s["volume_id"] = opts.VolumeID
+
+	if opts.Description != "" {
+		s["display_description"] = opts.Description
+	}
+	if opts.Force == true {
+		s["force"] = opts.Force
+	}
+	if opts.Metadata != nil {
+		s["metadata"] = opts.Metadata
+	}
+	if opts.Name != "" {
+		s["display_name"] = opts.Name
+	}
+
+	return map[string]interface{}{"snapshot": s}, nil
+}
+
+// Create will create a new Snapshot based on the values in CreateOpts. To
+// extract the Snapshot object from the response, call the Extract method on the
 // CreateResult.
-func Create(client *gophercloud.ServiceClient, opts *CreateOpts) CreateResult {
-	type snapshot struct {
-		Description *string                `json:"display_description,omitempty"`
-		Force       bool                   `json:"force,omitempty"`
-		Metadata    map[string]interface{} `json:"metadata,omitempty"`
-		Name        *string                `json:"display_name,omitempty"`
-		VolumeID    *string                `json:"volume_id,omitempty"`
-	}
-
-	type request struct {
-		Snapshot snapshot `json:"snapshot"`
-	}
-
-	reqBody := request{
-		Snapshot: snapshot{},
-	}
-
-	reqBody.Snapshot.Description = gophercloud.MaybeString(opts.Description)
-	reqBody.Snapshot.Name = gophercloud.MaybeString(opts.Name)
-	reqBody.Snapshot.VolumeID = gophercloud.MaybeString(opts.VolumeID)
-
-	reqBody.Snapshot.Force = opts.Force
-
+func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
 	var res CreateResult
+
+	reqBody, err := opts.ToSnapshotCreateMap()
+	if err != nil {
+		res.Err = err
+		return res
+	}
+
 	_, res.Err = perigee.Request("POST", createURL(client), perigee.Options{
 		MoreHeaders: client.Provider.AuthenticatedHeaders(),
 		OkCodes:     []int{200, 201},
@@ -63,8 +87,8 @@
 	return err
 }
 
-// Get retrieves the Snapshot with the provided ID. To extract the Snapshot object
-// from the response, call the Extract method on the GetResult.
+// Get retrieves the Snapshot with the provided ID. To extract the Snapshot
+// object from the response, call the Extract method on the GetResult.
 func Get(client *gophercloud.ServiceClient, id string) GetResult {
 	var res GetResult
 	_, res.Err = perigee.Request("GET", getURL(client, id), perigee.Options{
@@ -75,6 +99,12 @@
 	return res
 }
 
+// ListOptsBuilder allows extensions to add additional parameters to the List
+// request.
+type ListOptsBuilder interface {
+	ToSnapshotListQuery() (string, error)
+}
+
 // ListOpts hold options for listing Snapshots. It is passed to the
 // snapshots.List function.
 type ListOpts struct {
@@ -83,15 +113,25 @@
 	VolumeID string `q:"volume_id"`
 }
 
-// List returns Snapshots optionally limited by the conditions provided in ListOpts.
-func List(client *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager {
+// ToSnapshotListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToSnapshotListQuery() (string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	if err != nil {
+		return "", err
+	}
+	return q.String(), nil
+}
+
+// List returns Snapshots optionally limited by the conditions provided in
+// ListOpts.
+func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
 	url := listURL(client)
 	if opts != nil {
-		query, err := gophercloud.BuildQueryString(opts)
+		query, err := opts.ToSnapshotListQuery()
 		if err != nil {
 			return pagination.Pager{Err: err}
 		}
-		url += query.String()
+		url += query
 	}
 
 	createPage := func(r pagination.LastHTTPResponse) pagination.Page {
@@ -100,6 +140,12 @@
 	return pagination.NewPager(client, url, createPage)
 }
 
+// UpdateMetadataOptsBuilder allows extensions to add additional parameters to
+// the Update request.
+type UpdateMetadataOptsBuilder interface {
+	ToSnapshotUpdateMetadataMap() (map[string]interface{}, error)
+}
+
 // UpdateMetadataOpts contain options for updating an existing Snapshot. This
 // object is passed to the snapshots.Update function. For more information
 // about the parameters, see the Snapshot object.
@@ -107,20 +153,30 @@
 	Metadata map[string]interface{}
 }
 
+// ToSnapshotUpdateMetadataMap assembles a request body based on the contents of
+// an UpdateMetadataOpts.
+func (opts UpdateMetadataOpts) ToSnapshotUpdateMetadataMap() (map[string]interface{}, error) {
+	v := make(map[string]interface{})
+
+	if opts.Metadata != nil {
+		v["metadata"] = opts.Metadata
+	}
+
+	return v, nil
+}
+
 // UpdateMetadata will update the Snapshot with provided information. To
 // extract the updated Snapshot from the response, call the ExtractMetadata
 // method on the UpdateMetadataResult.
-func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts *UpdateMetadataOpts) UpdateMetadataResult {
-	type request struct {
-		Metadata map[string]interface{} `json:"metadata,omitempty"`
-	}
-
-	reqBody := request{}
-
-	reqBody.Metadata = opts.Metadata
-
+func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMetadataOptsBuilder) UpdateMetadataResult {
 	var res UpdateMetadataResult
 
+	reqBody, err := opts.ToSnapshotUpdateMetadataMap()
+	if err != nil {
+		res.Err = err
+		return res
+	}
+
 	_, res.Err = perigee.Request("PUT", updateMetadataURL(client, id), perigee.Options{
 		MoreHeaders: client.Provider.AuthenticatedHeaders(),
 		OkCodes:     []int{200},
diff --git a/openstack/blockstorage/v1/snapshots/requests_test.go b/openstack/blockstorage/v1/snapshots/requests_test.go
index d29cc0d..ddfa81b 100644
--- a/openstack/blockstorage/v1/snapshots/requests_test.go
+++ b/openstack/blockstorage/v1/snapshots/requests_test.go
@@ -119,6 +119,7 @@
 		th.TestJSONRequest(t, r, `
 {
     "snapshot": {
+				"volume_id": "1234",
         "display_name": "snapshot-001"
     }
 }
@@ -130,6 +131,7 @@
 		fmt.Fprintf(w, `
 {
     "snapshot": {
+				"volume_id": "1234",
         "display_name": "snapshot-001",
         "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22"
     }
@@ -137,10 +139,11 @@
 		`)
 	})
 
-	options := &CreateOpts{Name: "snapshot-001"}
+	options := &CreateOpts{VolumeID: "1234", Name: "snapshot-001"}
 	n, err := Create(ServiceClient(), options).Extract()
 	th.AssertNoErr(t, err)
 
+	th.AssertEquals(t, n.VolumeID, "1234")
 	th.AssertEquals(t, n.Name, "snapshot-001")
 	th.AssertEquals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22")
 }
diff --git a/openstack/blockstorage/v1/snapshots/results.go b/openstack/blockstorage/v1/snapshots/results.go
index d23090d..dc94a32 100644
--- a/openstack/blockstorage/v1/snapshots/results.go
+++ b/openstack/blockstorage/v1/snapshots/results.go
@@ -9,19 +9,32 @@
 
 // Snapshot contains all the information associated with an OpenStack Snapshot.
 type Snapshot struct {
-	Status           string            `mapstructure:"status"`              // currect status of the Snapshot
-	Name             string            `mapstructure:"display_name"`        // display name
-	Attachments      []string          `mapstructure:"attachments"`         // instances onto which the Snapshot is attached
-	AvailabilityZone string            `mapstructure:"availability_zone"`   // logical group
-	Bootable         string            `mapstructure:"bootable"`            // is the Snapshot bootable
-	CreatedAt        string            `mapstructure:"created_at"`          // date created
-	Description      string            `mapstructure:"display_discription"` // display description
-	VolumeType       string            `mapstructure:"volume_type"`         // see VolumeType object for more information
-	SnapshotID       string            `mapstructure:"snapshot_id"`         // ID of the Snapshot from which this Snapshot was created
-	SourceVolID      string            `mapstructure:"source_volid"`        // ID of the Volume from which this Snapshot was created
-	Metadata         map[string]string `mapstructure:"metadata"`            // user-defined key-value pairs
-	ID               string            `mapstructure:"id"`                  // unique identifier
-	Size             int               `mapstructure:"size"`                // size of the Snapshot, in GB
+	// Currect status of the Snapshot.
+	Status string `mapstructure:"status"`
+	// Display name.
+	Name string `mapstructure:"display_name"`
+	// Instances onto which the Snapshot is attached.
+	Attachments []string `mapstructure:"attachments"`
+	// Logical group.
+	AvailabilityZone string `mapstructure:"availability_zone"`
+	// Is the Snapshot bootable?
+	Bootable string `mapstructure:"bootable"`
+	// Date created.
+	CreatedAt string `mapstructure:"created_at"`
+	// Display description.
+	Description string `mapstructure:"display_discription"`
+	// See VolumeType object for more information.
+	VolumeType string `mapstructure:"volume_type"`
+	// ID of the Snapshot from which this Snapshot was created.
+	SnapshotID string `mapstructure:"snapshot_id"`
+	// ID of the Volume from which this Snapshot was created.
+	VolumeID string `mapstructure:"volume_id"`
+	// User-defined key-value pairs.
+	Metadata map[string]string `mapstructure:"metadata"`
+	// Unique identifier.
+	ID string `mapstructure:"id"`
+	// Size of the Snapshot, in GB.
+	Size int `mapstructure:"size"`
 }
 
 // CreateResult contains the response body and error from a Create request.
diff --git a/openstack/blockstorage/v1/snapshots/util_test.go b/openstack/blockstorage/v1/snapshots/util_test.go
new file mode 100644
index 0000000..46b452e
--- /dev/null
+++ b/openstack/blockstorage/v1/snapshots/util_test.go
@@ -0,0 +1,37 @@
+package snapshots
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+	"time"
+
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestWaitForStatus(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	th.Mux.HandleFunc("/snapshots/1234", func(w http.ResponseWriter, r *http.Request) {
+		time.Sleep(2 * time.Second)
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, `
+		{
+			"snapshot": {
+				"display_name": "snapshot-001",
+				"id": "1234",
+				"status":"available"
+			}
+		}`)
+	})
+
+	err := WaitForStatus(ServiceClient(), "1234", "available", 0)
+	if err == nil {
+		t.Errorf("Expected error: 'Time Out in WaitFor'")
+	}
+
+	err = WaitForStatus(ServiceClient(), "1234", "available", 3)
+	th.CheckNoErr(t, err)
+}
diff --git a/openstack/blockstorage/v1/volumes/requests.go b/openstack/blockstorage/v1/volumes/requests.go
index bca27db..042a33e 100644
--- a/openstack/blockstorage/v1/volumes/requests.go
+++ b/openstack/blockstorage/v1/volumes/requests.go
@@ -1,59 +1,90 @@
 package volumes
 
 import (
+	"fmt"
+
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
 
 	"github.com/racker/perigee"
 )
 
+// CreateOptsBuilder allows extensions to add additional parameters to the
+// Create request.
+type CreateOptsBuilder interface {
+	ToVolumeCreateMap() (map[string]interface{}, error)
+}
+
 // CreateOpts contains options for creating a Volume. This object is passed to
 // the volumes.Create function. For more information about these parameters,
 // see the Volume object.
 type CreateOpts struct {
-	Availability                     string            // OPTIONAL
-	Description                      string            // OPTIONAL
-	Metadata                         map[string]string // OPTIONAL
-	Name                             string            // OPTIONAL
-	Size                             int               // REQUIRED
-	SnapshotID, SourceVolID, ImageID string            // REQUIRED (one of them)
-	VolumeType                       string            // OPTIONAL
+	// OPTIONAL
+	Availability string
+	// OPTIONAL
+	Description string
+	// OPTIONAL
+	Metadata map[string]string
+	// OPTIONAL
+	Name string
+	// REQUIRED
+	Size int
+	// OPTIONAL
+	SnapshotID, SourceVolID, ImageID string
+	// OPTIONAL
+	VolumeType string
+}
+
+// ToVolumeCreateMap assembles a request body based on the contents of a
+// CreateOpts.
+func (opts CreateOpts) ToVolumeCreateMap() (map[string]interface{}, error) {
+	v := make(map[string]interface{})
+
+	if opts.Size == 0 {
+		return nil, fmt.Errorf("Required CreateOpts field 'Size' not set.")
+	}
+	v["size"] = opts.Size
+
+	if opts.Availability != "" {
+		v["availability_zone"] = opts.Availability
+	}
+	if opts.Description != "" {
+		v["display_description"] = opts.Description
+	}
+	if opts.ImageID != "" {
+		v["imageRef"] = opts.ImageID
+	}
+	if opts.Metadata != nil {
+		v["metadata"] = opts.Metadata
+	}
+	if opts.Name != "" {
+		v["display_name"] = opts.Name
+	}
+	if opts.SourceVolID != "" {
+		v["source_volid"] = opts.SourceVolID
+	}
+	if opts.SnapshotID != "" {
+		v["snapshot_id"] = opts.SnapshotID
+	}
+	if opts.VolumeType != "" {
+		v["volume_type"] = opts.VolumeType
+	}
+
+	return map[string]interface{}{"volume": v}, nil
 }
 
 // Create will create a new Volume based on the values in CreateOpts. To extract
-// the Volume object from the response, call the Extract method on the CreateResult.
-func Create(client *gophercloud.ServiceClient, opts *CreateOpts) CreateResult {
-
-	type volume struct {
-		Availability *string           `json:"availability_zone,omitempty"`
-		Description  *string           `json:"display_description,omitempty"`
-		ImageID      *string           `json:"imageRef,omitempty"`
-		Metadata     map[string]string `json:"metadata,omitempty"`
-		Name         *string           `json:"display_name,omitempty"`
-		Size         *int              `json:"size,omitempty"`
-		SnapshotID   *string           `json:"snapshot_id,omitempty"`
-		SourceVolID  *string           `json:"source_volid,omitempty"`
-		VolumeType   *string           `json:"volume_type,omitempty"`
-	}
-
-	type request struct {
-		Volume volume `json:"volume"`
-	}
-
-	reqBody := request{
-		Volume: volume{},
-	}
-
-	reqBody.Volume.Availability = gophercloud.MaybeString(opts.Availability)
-	reqBody.Volume.Description = gophercloud.MaybeString(opts.Description)
-	reqBody.Volume.ImageID = gophercloud.MaybeString(opts.ImageID)
-	reqBody.Volume.Name = gophercloud.MaybeString(opts.Name)
-	reqBody.Volume.Size = gophercloud.MaybeInt(opts.Size)
-	reqBody.Volume.SnapshotID = gophercloud.MaybeString(opts.SnapshotID)
-	reqBody.Volume.SourceVolID = gophercloud.MaybeString(opts.SourceVolID)
-	reqBody.Volume.VolumeType = gophercloud.MaybeString(opts.VolumeType)
-
+// the Volume object from the response, call the Extract method on the
+// CreateResult.
+func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
 	var res CreateResult
+
+	reqBody, err := opts.ToVolumeCreateMap()
+	if err != nil {
+		res.Err = err
+		return res
+	}
+
 	_, res.Err = perigee.Request("POST", createURL(client), perigee.Options{
 		MoreHeaders: client.Provider.AuthenticatedHeaders(),
 		ReqBody:     &reqBody,
@@ -72,8 +103,8 @@
 	return err
 }
 
-// Get retrieves the Volume with the provided ID. To extract the Volume object from
-// the response, call the Extract method on the GetResult.
+// Get retrieves the Volume with the provided ID. To extract the Volume object
+// from the response, call the Extract method on the GetResult.
 func Get(client *gophercloud.ServiceClient, id string) GetResult {
 	var res GetResult
 	_, res.Err = perigee.Request("GET", getURL(client, id), perigee.Options{
@@ -84,62 +115,97 @@
 	return res
 }
 
+// ListOptsBuilder allows extensions to add additional parameters to the List
+// request.
+type ListOptsBuilder interface {
+	ToVolumeListQuery() (string, error)
+}
+
 // ListOpts holds options for listing Volumes. It is passed to the volumes.List
 // function.
 type ListOpts struct {
-	AllTenants bool              `q:"all_tenants"` // admin-only option. Set it to true to see all tenant volumes.
-	Metadata   map[string]string `q:"metadata"`    // List only volumes that contain Metadata.
-	Name       string            `q:"name"`        // List only volumes that have Name as the display name.
-	Status     string            `q:"status"`      // List only volumes that have a status of Status.
+	// admin-only option. Set it to true to see all tenant volumes.
+	AllTenants bool `q:"all_tenants"`
+	// List only volumes that contain Metadata.
+	Metadata map[string]string `q:"metadata"`
+	// List only volumes that have Name as the display name.
+	Name string `q:"name"`
+	// List only volumes that have a status of Status.
+	Status string `q:"status"`
+}
+
+// ToVolumeListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToVolumeListQuery() (string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	if err != nil {
+		return "", err
+	}
+	return q.String(), nil
 }
 
 // List returns Volumes optionally limited by the conditions provided in ListOpts.
-func List(client *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager {
+func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
 	url := listURL(client)
 	if opts != nil {
-		query, err := gophercloud.BuildQueryString(opts)
+		query, err := opts.ToVolumeListQuery()
 		if err != nil {
 			return pagination.Pager{Err: err}
 		}
-		url += query.String()
+		url += query
 	}
 	createPage := func(r pagination.LastHTTPResponse) pagination.Page {
 		return ListResult{pagination.SinglePageBase(r)}
 	}
-	return pagination.NewPager(client, listURL(client), createPage)
+	return pagination.NewPager(client, url, createPage)
+}
+
+// UpdateOptsBuilder allows extensions to add additional parameters to the
+// Update request.
+type UpdateOptsBuilder interface {
+	ToVolumeUpdateMap() (map[string]interface{}, error)
 }
 
 // UpdateOpts contain options for updating an existing Volume. This object is passed
 // to the volumes.Update function. For more information about the parameters, see
 // the Volume object.
 type UpdateOpts struct {
-	Name        string            // OPTIONAL
-	Description string            // OPTIONAL
-	Metadata    map[string]string // OPTIONAL
+	// OPTIONAL
+	Name string
+	// OPTIONAL
+	Description string
+	// OPTIONAL
+	Metadata map[string]string
+}
+
+// ToVolumeUpdateMap assembles a request body based on the contents of an
+// UpdateOpts.
+func (opts UpdateOpts) ToVolumeUpdateMap() (map[string]interface{}, error) {
+	v := make(map[string]interface{})
+
+	if opts.Description != "" {
+		v["display_description"] = opts.Description
+	}
+	if opts.Metadata != nil {
+		v["metadata"] = opts.Metadata
+	}
+	if opts.Name != "" {
+		v["display_name"] = opts.Name
+	}
+
+	return map[string]interface{}{"volume": v}, nil
 }
 
 // Update will update the Volume with provided information. To extract the updated
 // Volume from the response, call the Extract method on the UpdateResult.
-func Update(client *gophercloud.ServiceClient, id string, opts *UpdateOpts) UpdateResult {
-	type update struct {
-		Description *string           `json:"display_description,omitempty"`
-		Metadata    map[string]string `json:"metadata,omitempty"`
-		Name        *string           `json:"display_name,omitempty"`
-	}
-
-	type request struct {
-		Volume update `json:"volume"`
-	}
-
-	reqBody := request{
-		Volume: update{},
-	}
-
-	reqBody.Volume.Description = gophercloud.MaybeString(opts.Description)
-	reqBody.Volume.Name = gophercloud.MaybeString(opts.Name)
-
+func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
 	var res UpdateResult
 
+	reqBody, err := opts.ToVolumeUpdateMap()
+	if err != nil {
+		res.Err = err
+		return res
+	}
+
 	_, res.Err = perigee.Request("PUT", updateURL(client, id), perigee.Options{
 		MoreHeaders: client.Provider.AuthenticatedHeaders(),
 		OkCodes:     []int{200},
diff --git a/openstack/blockstorage/v1/volumes/requests_test.go b/openstack/blockstorage/v1/volumes/requests_test.go
index 54ff91d..7cd37d5 100644
--- a/openstack/blockstorage/v1/volumes/requests_test.go
+++ b/openstack/blockstorage/v1/volumes/requests_test.go
@@ -119,7 +119,7 @@
 		th.TestJSONRequest(t, r, `
 {
     "volume": {
-        "display_name": "vol-001"
+        "size": 4
     }
 }
 			`)
@@ -130,18 +130,18 @@
 		fmt.Fprintf(w, `
 {
     "volume": {
-        "display_name": "vol-001",
+        "size": 4,
         "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22"
     }
 }
 		`)
 	})
 
-	options := &CreateOpts{Name: "vol-001"}
+	options := &CreateOpts{Size: 4}
 	n, err := Create(ServiceClient(), options).Extract()
 	th.AssertNoErr(t, err)
 
-	th.AssertEquals(t, n.Name, "vol-001")
+	th.AssertEquals(t, n.Size, 4)
 	th.AssertEquals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22")
 }
 
@@ -158,3 +158,27 @@
 	err := Delete(ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22")
 	th.AssertNoErr(t, err)
 }
+
+func TestUpdate(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	th.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "PUT")
+		th.TestHeader(t, r, "X-Auth-Token", TokenID)
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, `
+		{
+			"volume": {
+				"display_name": "vol-002",
+				"id": "d32019d3-bc6e-4319-9c1d-6722fc136a22"
+		    }
+		}
+		`)
+	})
+
+	options := &UpdateOpts{Name: "vol-002"}
+	v, err := Update(ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", options).Extract()
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, "vol-002", v.Name)
+}
diff --git a/openstack/blockstorage/v1/volumes/util_test.go b/openstack/blockstorage/v1/volumes/util_test.go
new file mode 100644
index 0000000..7de1326
--- /dev/null
+++ b/openstack/blockstorage/v1/volumes/util_test.go
@@ -0,0 +1,37 @@
+package volumes
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+	"time"
+
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestWaitForStatus(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	th.Mux.HandleFunc("/volumes/1234", func(w http.ResponseWriter, r *http.Request) {
+		time.Sleep(2 * time.Second)
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, `
+		{
+			"volume": {
+				"display_name": "vol-001",
+				"id": "1234",
+				"status":"available"
+			}
+		}`)
+	})
+
+	err := WaitForStatus(ServiceClient(), "1234", "available", 0)
+	if err == nil {
+		t.Errorf("Expected error: 'Time Out in WaitFor'")
+	}
+
+	err = WaitForStatus(ServiceClient(), "1234", "available", 3)
+	th.CheckNoErr(t, err)
+}
diff --git a/openstack/blockstorage/v1/volumetypes/requests.go b/openstack/blockstorage/v1/volumetypes/requests.go
index afe650d..d4f880f 100644
--- a/openstack/blockstorage/v1/volumetypes/requests.go
+++ b/openstack/blockstorage/v1/volumetypes/requests.go
@@ -6,6 +6,12 @@
 	"github.com/rackspace/gophercloud/pagination"
 )
 
+// CreateOptsBuilder allows extensions to add additional parameters to the
+// Create request.
+type CreateOptsBuilder interface {
+	ToVolumeTypeCreateMap() (map[string]interface{}, error)
+}
+
 // CreateOpts are options for creating a volume type.
 type CreateOpts struct {
 	// OPTIONAL. See VolumeType.
@@ -14,26 +20,31 @@
 	Name string
 }
 
-// Create will create a new volume, optionally wih CreateOpts. To extract the
-// created volume type object, call the Extract method on the CreateResult.
-func Create(client *gophercloud.ServiceClient, opts *CreateOpts) CreateResult {
-	type volumeType struct {
-		ExtraSpecs map[string]interface{} `json:"extra_specs,omitempty"`
-		Name       *string                `json:"name,omitempty"`
+// ToVolumeTypeCreateMap casts a CreateOpts struct to a map.
+func (opts CreateOpts) ToVolumeTypeCreateMap() (map[string]interface{}, error) {
+	vt := make(map[string]interface{})
+
+	if opts.ExtraSpecs != nil {
+		vt["extra_specs"] = opts.ExtraSpecs
+	}
+	if opts.Name != "" {
+		vt["name"] = opts.Name
 	}
 
-	type request struct {
-		VolumeType volumeType `json:"volume_type"`
-	}
+	return map[string]interface{}{"volume_type": vt}, nil
+}
 
-	reqBody := request{
-		VolumeType: volumeType{},
-	}
-
-	reqBody.VolumeType.Name = gophercloud.MaybeString(opts.Name)
-	reqBody.VolumeType.ExtraSpecs = opts.ExtraSpecs
-
+// Create will create a new volume. To extract the created volume type object,
+// call the Extract method on the CreateResult.
+func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
 	var res CreateResult
+
+	reqBody, err := opts.ToVolumeTypeCreateMap()
+	if err != nil {
+		res.Err = err
+		return res
+	}
+
 	_, res.Err = perigee.Request("POST", createURL(client), perigee.Options{
 		MoreHeaders: client.Provider.AuthenticatedHeaders(),
 		OkCodes:     []int{200, 201},
diff --git a/openstack/compute/v2/flavors/requests.go b/openstack/compute/v2/flavors/requests.go
index 47eb172..7af11fc 100644
--- a/openstack/compute/v2/flavors/requests.go
+++ b/openstack/compute/v2/flavors/requests.go
@@ -6,41 +6,65 @@
 	"github.com/rackspace/gophercloud/pagination"
 )
 
-// ListFilterOptions helps control the results returned by the List() function.
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+	ToFlavorListQuery() (string, error)
+}
+
+// ListOpts helps control the results returned by the List() function.
 // For example, a flavor with a minDisk field of 10 will not be returned if you specify MinDisk set to 20.
 // Typically, software will use the last ID of the previous call to List to set the Marker for the current call.
-type ListFilterOptions struct {
+type ListOpts struct {
 
 	// ChangesSince, if provided, instructs List to return only those things which have changed since the timestamp provided.
-	ChangesSince string
+	ChangesSince string `q:"changes-since"`
 
 	// MinDisk and MinRAM, if provided, elides flavors which do not meet your criteria.
-	MinDisk, MinRAM int
+	MinDisk int `q:"minDisk"`
+	MinRAM  int `q:"minRam"`
 
 	// Marker and Limit control paging.
 	// Marker instructs List where to start listing from.
-	Marker string
+	Marker string `q:"marker"`
 
 	// Limit instructs List to refrain from sending excessively large lists of flavors.
-	Limit int
+	Limit int `q:"limit"`
+}
+
+// ToFlavorListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToFlavorListQuery() (string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	if err != nil {
+		return "", err
+	}
+	return q.String(), nil
 }
 
 // List instructs OpenStack to provide a list of flavors.
 // You may provide criteria by which List curtails its results for easier processing.
-// See ListFilterOptions for more details.
-func List(client *gophercloud.ServiceClient, lfo ListFilterOptions) pagination.Pager {
+// See ListOpts for more details.
+func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+	url := listURL(client)
+	if opts != nil {
+		query, err := opts.ToFlavorListQuery()
+		if err != nil {
+			return pagination.Pager{Err: err}
+		}
+		url += query
+	}
 	createPage := func(r pagination.LastHTTPResponse) pagination.Page {
 		return FlavorPage{pagination.LinkedPageBase{LastHTTPResponse: r}}
 	}
 
-	return pagination.NewPager(client, listURL(client, lfo), createPage)
+	return pagination.NewPager(client, url, createPage)
 }
 
 // Get instructs OpenStack to provide details on a single flavor, identified by its ID.
 // Use ExtractFlavor to convert its result into a Flavor.
 func Get(client *gophercloud.ServiceClient, id string) GetResult {
 	var gr GetResult
-	gr.Err = perigee.Get(flavorURL(client, id), perigee.Options{
+	gr.Err = perigee.Get(getURL(client, id), perigee.Options{
 		Results:     &gr.Resp,
 		MoreHeaders: client.Provider.AuthenticatedHeaders(),
 	})
diff --git a/openstack/compute/v2/flavors/requests_test.go b/openstack/compute/v2/flavors/requests_test.go
index e1b6b4f..bc9b82e 100644
--- a/openstack/compute/v2/flavors/requests_test.go
+++ b/openstack/compute/v2/flavors/requests_test.go
@@ -6,27 +6,20 @@
 	"reflect"
 	"testing"
 
-	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
-	"github.com/rackspace/gophercloud/testhelper"
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
 )
 
 const tokenID = "blerb"
 
-func serviceClient() *gophercloud.ServiceClient {
-	return &gophercloud.ServiceClient{
-		Provider: &gophercloud.ProviderClient{TokenID: tokenID},
-		Endpoint: testhelper.Endpoint(),
-	}
-}
-
 func TestListFlavors(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
-	testhelper.Mux.HandleFunc("/flavors/detail", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "GET")
-		testhelper.TestHeader(t, r, "X-Auth-Token", tokenID)
+	th.Mux.HandleFunc("/flavors/detail", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
 
 		w.Header().Add("Content-Type", "application/json")
 		r.ParseForm()
@@ -58,7 +51,7 @@
 							}
 						]
 					}
-				`, testhelper.Server.URL)
+				`, th.Server.URL)
 		case "2":
 			fmt.Fprintf(w, `{ "flavors": [] }`)
 		default:
@@ -66,9 +59,8 @@
 		}
 	})
 
-	client := serviceClient()
 	pages := 0
-	err := List(client, ListFilterOptions{}).EachPage(func(page pagination.Page) (bool, error) {
+	err := List(fake.ServiceClient(), &ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
 		pages++
 
 		actual, err := ExtractFlavors(page)
@@ -96,12 +88,12 @@
 }
 
 func TestGetFlavor(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
-	testhelper.Mux.HandleFunc("/flavors/12345", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "GET")
-		testhelper.TestHeader(t, r, "X-Auth-Token", tokenID)
+	th.Mux.HandleFunc("/flavors/12345", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
 
 		w.Header().Add("Content-Type", "application/json")
 		fmt.Fprintf(w, `
@@ -118,8 +110,7 @@
 		`)
 	})
 
-	client := serviceClient()
-	actual, err := Get(client, "12345").Extract()
+	actual, err := Get(fake.ServiceClient(), "12345").Extract()
 	if err != nil {
 		t.Fatalf("Unable to get flavor: %v", err)
 	}
diff --git a/openstack/compute/v2/flavors/urls.go b/openstack/compute/v2/flavors/urls.go
index 9e5b562..683c107 100644
--- a/openstack/compute/v2/flavors/urls.go
+++ b/openstack/compute/v2/flavors/urls.go
@@ -1,37 +1,13 @@
 package flavors
 
 import (
-	"fmt"
-	"net/url"
-	"strconv"
-
 	"github.com/rackspace/gophercloud"
 )
 
-func listURL(client *gophercloud.ServiceClient, lfo ListFilterOptions) string {
-	v := url.Values{}
-	if lfo.ChangesSince != "" {
-		v.Set("changes-since", lfo.ChangesSince)
-	}
-	if lfo.MinDisk != 0 {
-		v.Set("minDisk", strconv.Itoa(lfo.MinDisk))
-	}
-	if lfo.MinRAM != 0 {
-		v.Set("minRam", strconv.Itoa(lfo.MinRAM))
-	}
-	if lfo.Marker != "" {
-		v.Set("marker", lfo.Marker)
-	}
-	if lfo.Limit != 0 {
-		v.Set("limit", strconv.Itoa(lfo.Limit))
-	}
-	tail := ""
-	if len(v) > 0 {
-		tail = fmt.Sprintf("?%s", v.Encode())
-	}
-	return client.ServiceURL("flavors", "detail") + tail
+func getURL(client *gophercloud.ServiceClient, id string) string {
+	return client.ServiceURL("flavors", id)
 }
 
-func flavorURL(client *gophercloud.ServiceClient, id string) string {
-	return client.ServiceURL("flavors", id)
+func listURL(client *gophercloud.ServiceClient) string {
+	return client.ServiceURL("flavors", "detail")
 }
diff --git a/openstack/compute/v2/flavors/urls_test.go b/openstack/compute/v2/flavors/urls_test.go
new file mode 100644
index 0000000..069da24
--- /dev/null
+++ b/openstack/compute/v2/flavors/urls_test.go
@@ -0,0 +1,26 @@
+package flavors
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+const endpoint = "http://localhost:57909/"
+
+func endpointClient() *gophercloud.ServiceClient {
+	return &gophercloud.ServiceClient{Endpoint: endpoint}
+}
+
+func TestGetURL(t *testing.T) {
+	actual := getURL(endpointClient(), "foo")
+	expected := endpoint + "flavors/foo"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestListURL(t *testing.T) {
+	actual := listURL(endpointClient())
+	expected := endpoint + "flavors/detail"
+	th.CheckEquals(t, expected, actual)
+}
diff --git a/openstack/compute/v2/images/requests.go b/openstack/compute/v2/images/requests.go
index a887cc6..603909c 100644
--- a/openstack/compute/v2/images/requests.go
+++ b/openstack/compute/v2/images/requests.go
@@ -1,25 +1,68 @@
 package images
 
 import (
-	"github.com/racker/perigee"
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
+
+	"github.com/racker/perigee"
 )
 
-// List enumerates the available images.
-func List(client *gophercloud.ServiceClient) pagination.Pager {
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+	ToImageListQuery() (string, error)
+}
+
+// ListOpts contain options for limiting the number of Images returned from a call to ListDetail.
+type ListOpts struct {
+	// When the image last changed status (in date-time format).
+	ChangesSince string `q:"changes-since"`
+	// The number of Images to return.
+	Limit int `q:"limit"`
+	// UUID of the Image at which to set a marker.
+	Marker string `q:"marker"`
+	// The name of the Image.
+	Name string `q:"name:"`
+	// The name of the Server (in URL format).
+	Server string `q:"server"`
+	// The current status of the Image.
+	Status string `q:"status"`
+	// The value of the type of image (e.g. BASE, SERVER, ALL)
+	Type string `q:"type"`
+}
+
+// ToImageListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToImageListQuery() (string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	if err != nil {
+		return "", err
+	}
+	return q.String(), nil
+}
+
+// ListDetail enumerates the available images.
+func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+	url := listDetailURL(client)
+	if opts != nil {
+		query, err := opts.ToImageListQuery()
+		if err != nil {
+			return pagination.Pager{Err: err}
+		}
+		url += query
+	}
+
 	createPage := func(r pagination.LastHTTPResponse) pagination.Page {
 		return ImagePage{pagination.LinkedPageBase{LastHTTPResponse: r}}
 	}
 
-	return pagination.NewPager(client, listURL(client), createPage)
+	return pagination.NewPager(client, url, createPage)
 }
 
 // Get acquires additional detail about a specific image by ID.
 // Use ExtractImage() to intepret the result as an openstack Image.
 func Get(client *gophercloud.ServiceClient, id string) GetResult {
 	var result GetResult
-	_, result.Err = perigee.Request("GET", imageURL(client, id), perigee.Options{
+	_, result.Err = perigee.Request("GET", getURL(client, id), perigee.Options{
 		MoreHeaders: client.Provider.AuthenticatedHeaders(),
 		Results:     &result.Resp,
 		OkCodes:     []int{200},
diff --git a/openstack/compute/v2/images/requests_test.go b/openstack/compute/v2/images/requests_test.go
index 396c21f..9a05f97 100644
--- a/openstack/compute/v2/images/requests_test.go
+++ b/openstack/compute/v2/images/requests_test.go
@@ -1,32 +1,24 @@
 package images
 
 import (
+	"encoding/json"
 	"fmt"
 	"net/http"
 	"reflect"
 	"testing"
 
-	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
-	"github.com/rackspace/gophercloud/testhelper"
+	th "github.com/rackspace/gophercloud/testhelper"
+	fake "github.com/rackspace/gophercloud/testhelper/client"
 )
 
-const tokenID = "aaaaaa"
-
-func serviceClient() *gophercloud.ServiceClient {
-	return &gophercloud.ServiceClient{
-		Provider: &gophercloud.ProviderClient{TokenID: tokenID},
-		Endpoint: testhelper.Endpoint(),
-	}
-}
-
 func TestListImages(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
-	testhelper.Mux.HandleFunc("/images/detail", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "GET")
-		testhelper.TestHeader(t, r, "X-Auth-Token", tokenID)
+	th.Mux.HandleFunc("/images/detail", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
 
 		w.Header().Add("Content-Type", "application/json")
 		r.ParseForm()
@@ -70,9 +62,9 @@
 		}
 	})
 
-	client := serviceClient()
 	pages := 0
-	err := List(client).EachPage(func(page pagination.Page) (bool, error) {
+	options := &ListOpts{Limit: 2}
+	err := ListDetail(fake.ServiceClient(), options).EachPage(func(page pagination.Page) (bool, error) {
 		pages++
 
 		actual, err := ExtractImages(page)
@@ -119,12 +111,12 @@
 }
 
 func TestGetImage(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
-	testhelper.Mux.HandleFunc("/images/12345678", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "GET")
-		testhelper.TestHeader(t, r, "X-Auth-Token", tokenID)
+	th.Mux.HandleFunc("/images/12345678", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
 
 		w.Header().Add("Content-Type", "application/json")
 		fmt.Fprintf(w, `
@@ -145,8 +137,7 @@
 		`)
 	})
 
-	client := serviceClient()
-	actual, err := Get(client, "12345678").Extract()
+	actual, err := Get(fake.ServiceClient(), "12345678").Extract()
 	if err != nil {
 		t.Fatalf("Unexpected error from Get: %v", err)
 	}
@@ -166,3 +157,19 @@
 		t.Errorf("Expected %#v, but got %#v", expected, actual)
 	}
 }
+
+func TestNextPageURL(t *testing.T) {
+	var page ImagePage
+	var body map[string]interface{}
+	bodyString := []byte(`{"images":{"links":[{"href":"http://192.154.23.87/12345/images/image3","rel":"bookmark"}]}, "images_links":[{"href":"http://192.154.23.87/12345/images/image4","rel":"next"}]}`)
+	err := json.Unmarshal(bodyString, &body)
+	if err != nil {
+		t.Fatalf("Error unmarshaling data into page body: %v", err)
+	}
+	page.Body = body
+
+	expected := "http://192.154.23.87/12345/images/image4"
+	actual, err := page.NextPageURL()
+	th.AssertNoErr(t, err)
+	th.CheckEquals(t, expected, actual)
+}
diff --git a/openstack/compute/v2/images/urls.go b/openstack/compute/v2/images/urls.go
index 4ae2269..9b3c86d 100644
--- a/openstack/compute/v2/images/urls.go
+++ b/openstack/compute/v2/images/urls.go
@@ -2,10 +2,10 @@
 
 import "github.com/rackspace/gophercloud"
 
-func listURL(client *gophercloud.ServiceClient) string {
+func listDetailURL(client *gophercloud.ServiceClient) string {
 	return client.ServiceURL("images", "detail")
 }
 
-func imageURL(client *gophercloud.ServiceClient, id string) string {
+func getURL(client *gophercloud.ServiceClient, id string) string {
 	return client.ServiceURL("images", id)
 }
diff --git a/openstack/compute/v2/images/urls_test.go b/openstack/compute/v2/images/urls_test.go
new file mode 100644
index 0000000..b1ab3d6
--- /dev/null
+++ b/openstack/compute/v2/images/urls_test.go
@@ -0,0 +1,26 @@
+package images
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+const endpoint = "http://localhost:57909/"
+
+func endpointClient() *gophercloud.ServiceClient {
+	return &gophercloud.ServiceClient{Endpoint: endpoint}
+}
+
+func TestGetURL(t *testing.T) {
+	actual := getURL(endpointClient(), "foo")
+	expected := endpoint + "images/foo"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestListDetailURL(t *testing.T) {
+	actual := listDetailURL(endpointClient())
+	expected := endpoint + "images/detail"
+	th.CheckEquals(t, expected, actual)
+}
diff --git a/openstack/compute/v2/servers/requests.go b/openstack/compute/v2/servers/requests.go
index 20ca52e..6f626be 100644
--- a/openstack/compute/v2/servers/requests.go
+++ b/openstack/compute/v2/servers/requests.go
@@ -15,7 +15,7 @@
 		return ServerPage{pagination.LinkedPageBase{LastHTTPResponse: r}}
 	}
 
-	return pagination.NewPager(client, detailURL(client), createPage)
+	return pagination.NewPager(client, listDetailURL(client), createPage)
 }
 
 // CreateOptsBuilder describes struct types that can be accepted by the Create call.
@@ -140,7 +140,7 @@
 
 // Delete requests that a server previously provisioned be removed from your account.
 func Delete(client *gophercloud.ServiceClient, id string) error {
-	_, err := perigee.Request("DELETE", serverURL(client, id), perigee.Options{
+	_, err := perigee.Request("DELETE", deleteURL(client, id), perigee.Options{
 		MoreHeaders: client.Provider.AuthenticatedHeaders(),
 		OkCodes:     []int{204},
 	})
@@ -150,15 +150,15 @@
 // Get requests details on a single server, by ID.
 func Get(client *gophercloud.ServiceClient, id string) GetResult {
 	var result GetResult
-	_, result.Err = perigee.Request("GET", serverURL(client, id), perigee.Options{
+	_, result.Err = perigee.Request("GET", getURL(client, id), perigee.Options{
 		Results:     &result.Resp,
 		MoreHeaders: client.Provider.AuthenticatedHeaders(),
 	})
 	return result
 }
 
-// UpdateOptsLike allows extentions to add additional attributes to the Update request.
-type UpdateOptsLike interface {
+// UpdateOptsBuilder allows extentions to add additional attributes to the Update request.
+type UpdateOptsBuilder interface {
 	ToServerUpdateMap() map[string]interface{}
 }
 
@@ -192,9 +192,9 @@
 }
 
 // Update requests that various attributes of the indicated server be changed.
-func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsLike) UpdateResult {
+func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
 	var result UpdateResult
-	_, result.Err = perigee.Request("PUT", serverURL(client, id), perigee.Options{
+	_, result.Err = perigee.Request("PUT", updateURL(client, id), perigee.Options{
 		Results:     &result.Resp,
 		ReqBody:     opts.ToServerUpdateMap(),
 		MoreHeaders: client.Provider.AuthenticatedHeaders(),
diff --git a/openstack/compute/v2/servers/urls.go b/openstack/compute/v2/servers/urls.go
index 52be73e..57587ab 100644
--- a/openstack/compute/v2/servers/urls.go
+++ b/openstack/compute/v2/servers/urls.go
@@ -2,18 +2,30 @@
 
 import "github.com/rackspace/gophercloud"
 
-func listURL(client *gophercloud.ServiceClient) string {
+func createURL(client *gophercloud.ServiceClient) string {
 	return client.ServiceURL("servers")
 }
 
-func detailURL(client *gophercloud.ServiceClient) string {
+func listURL(client *gophercloud.ServiceClient) string {
+	return createURL(client)
+}
+
+func listDetailURL(client *gophercloud.ServiceClient) string {
 	return client.ServiceURL("servers", "detail")
 }
 
-func serverURL(client *gophercloud.ServiceClient, id string) string {
+func deleteURL(client *gophercloud.ServiceClient, id string) string {
 	return client.ServiceURL("servers", id)
 }
 
+func getURL(client *gophercloud.ServiceClient, id string) string {
+	return deleteURL(client, id)
+}
+
+func updateURL(client *gophercloud.ServiceClient, id string) string {
+	return deleteURL(client, id)
+}
+
 func actionURL(client *gophercloud.ServiceClient, id string) string {
 	return client.ServiceURL("servers", id, "action")
 }
diff --git a/openstack/compute/v2/servers/urls_test.go b/openstack/compute/v2/servers/urls_test.go
new file mode 100644
index 0000000..cc895c9
--- /dev/null
+++ b/openstack/compute/v2/servers/urls_test.go
@@ -0,0 +1,56 @@
+package servers
+
+import (
+	"testing"
+
+	"github.com/rackspace/gophercloud"
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+const endpoint = "http://localhost:57909"
+
+func endpointClient() *gophercloud.ServiceClient {
+	return &gophercloud.ServiceClient{Endpoint: endpoint}
+}
+
+func TestCreateURL(t *testing.T) {
+	actual := createURL(endpointClient())
+	expected := endpoint + "servers"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestListURL(t *testing.T) {
+	actual := listURL(endpointClient())
+	expected := endpoint + "servers"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestListDetailURL(t *testing.T) {
+	actual := listDetailURL(endpointClient())
+	expected := endpoint + "servers/detail"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestDeleteURL(t *testing.T) {
+	actual := deleteURL(endpointClient(), "foo")
+	expected := endpoint + "servers/foo"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestGetURL(t *testing.T) {
+	actual := getURL(endpointClient(), "foo")
+	expected := endpoint + "servers/foo"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestUpdateURL(t *testing.T) {
+	actual := updateURL(endpointClient(), "foo")
+	expected := endpoint + "servers/foo"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestActionURL(t *testing.T) {
+	actual := actionURL(endpointClient(), "foo")
+	expected := endpoint + "servers/foo/action"
+	th.CheckEquals(t, expected, actual)
+}
diff --git a/openstack/networking/v2/extensions/external/requests.go b/openstack/networking/v2/extensions/external/requests.go
index afdd428..2f04593 100644
--- a/openstack/networking/v2/extensions/external/requests.go
+++ b/openstack/networking/v2/extensions/external/requests.go
@@ -24,12 +24,15 @@
 }
 
 // ToNetworkCreateMap casts a CreateOpts struct to a map.
-func (o CreateOpts) ToNetworkCreateMap() map[string]map[string]interface{} {
-	outer := o.Parent.ToNetworkCreateMap()
+func (o CreateOpts) ToNetworkCreateMap() (map[string]interface{}, error) {
+	outer, err := o.Parent.ToNetworkCreateMap()
+	if err != nil {
+		return nil, err
+	}
 
-	outer["network"]["router:external"] = o.External
+	outer["network"].(map[string]interface{})["router:external"] = o.External
 
-	return outer
+	return outer, nil
 }
 
 // UpdateOpts is the structure used when updating existing external network
@@ -41,10 +44,13 @@
 }
 
 // ToNetworkUpdateMap casts an UpdateOpts struct to a map.
-func (o UpdateOpts) ToNetworkUpdateMap() map[string]map[string]interface{} {
-	outer := o.Parent.ToNetworkUpdateMap()
+func (o UpdateOpts) ToNetworkUpdateMap() (map[string]interface{}, error) {
+	outer, err := o.Parent.ToNetworkUpdateMap()
+	if err != nil {
+		return nil, err
+	}
 
-	outer["network"]["router:external"] = o.External
+	outer["network"].(map[string]interface{})["router:external"] = o.External
 
-	return outer
+	return outer, nil
 }
diff --git a/openstack/networking/v2/networks/requests.go b/openstack/networking/v2/networks/requests.go
index 39a151a..8cfe4e0 100644
--- a/openstack/networking/v2/networks/requests.go
+++ b/openstack/networking/v2/networks/requests.go
@@ -1,9 +1,10 @@
 package networks
 
 import (
-	"github.com/racker/perigee"
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
+
+	"github.com/racker/perigee"
 )
 
 // AdminState gives users a solid type to work with for create and update
@@ -26,6 +27,12 @@
 	TenantID     string
 }
 
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+	ToNetworkListQuery() (string, error)
+}
+
 // ListOpts allows the filtering and sorting of paginated collections through
 // the API. Filtering is achieved by passing in struct field values that map to
 // the network attributes you want to see returned. SortKey allows you to sort
@@ -44,17 +51,29 @@
 	SortDir      string `q:"sort_dir"`
 }
 
+// ToNetworkListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToNetworkListQuery() (string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	if err != nil {
+		return "", err
+	}
+	return q.String(), nil
+}
+
 // List returns a Pager which allows you to iterate over a collection of
 // networks. It accepts a ListOpts struct, which allows you to filter and sort
 // the returned collection for greater efficiency.
-func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager {
-	// Build query parameters
-	q, err := gophercloud.BuildQueryString(&opts)
-	if err != nil {
-		return pagination.Pager{Err: err}
+func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+	url := listURL(c)
+	if opts != nil {
+		query, err := opts.ToNetworkListQuery()
+		if err != nil {
+			return pagination.Pager{Err: err}
+		}
+		url += query
 	}
-	u := listURL(c) + q.String()
-	return pagination.NewPager(c, u, func(r pagination.LastHTTPResponse) pagination.Page {
+
+	return pagination.NewPager(c, url, func(r pagination.LastHTTPResponse) pagination.Page {
 		return NetworkPage{pagination.LinkedPageBase{LastHTTPResponse: r}}
 	})
 }
@@ -75,7 +94,7 @@
 // extensions decorate or modify the common logic, it is useful for them to
 // satisfy a basic interface in order for them to be used.
 type CreateOptsBuilder interface {
-	ToNetworkCreateMap() map[string]map[string]interface{}
+	ToNetworkCreateMap() (map[string]interface{}, error)
 }
 
 // CreateOpts is the common options struct used in this package's Create
@@ -83,26 +102,23 @@
 type CreateOpts networkOpts
 
 // ToNetworkCreateMap casts a CreateOpts struct to a map.
-func (o CreateOpts) ToNetworkCreateMap() map[string]map[string]interface{} {
-	inner := make(map[string]interface{})
+func (opts CreateOpts) ToNetworkCreateMap() (map[string]interface{}, error) {
+	n := make(map[string]interface{})
 
-	if o.AdminStateUp != nil {
-		inner["admin_state_up"] = &o.AdminStateUp
+	if opts.AdminStateUp != nil {
+		n["admin_state_up"] = &opts.AdminStateUp
 	}
-	if o.Name != "" {
-		inner["name"] = o.Name
+	if opts.Name != "" {
+		n["name"] = opts.Name
 	}
-	if o.Shared != nil {
-		inner["shared"] = &o.Shared
+	if opts.Shared != nil {
+		n["shared"] = &opts.Shared
 	}
-	if o.TenantID != "" {
-		inner["tenant_id"] = o.TenantID
+	if opts.TenantID != "" {
+		n["tenant_id"] = opts.TenantID
 	}
 
-	outer := make(map[string]map[string]interface{})
-	outer["network"] = inner
-
-	return outer
+	return map[string]interface{}{"network": n}, nil
 }
 
 // Create accepts a CreateOpts struct and creates a new network using the values
@@ -115,7 +131,11 @@
 func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
 	var res CreateResult
 
-	reqBody := opts.ToNetworkCreateMap()
+	reqBody, err := opts.ToNetworkCreateMap()
+	if err != nil {
+		res.Err = err
+		return res
+	}
 
 	// Send request to API
 	_, res.Err = perigee.Request("POST", createURL(c), perigee.Options{
@@ -132,7 +152,7 @@
 // extensions decorate or modify the common logic, it is useful for them to
 // satisfy a basic interface in order for them to be used.
 type UpdateOptsBuilder interface {
-	ToNetworkUpdateMap() map[string]map[string]interface{}
+	ToNetworkUpdateMap() (map[string]interface{}, error)
 }
 
 // UpdateOpts is the common options struct used in this package's Update
@@ -140,23 +160,20 @@
 type UpdateOpts networkOpts
 
 // ToNetworkUpdateMap casts a UpdateOpts struct to a map.
-func (o UpdateOpts) ToNetworkUpdateMap() map[string]map[string]interface{} {
-	inner := make(map[string]interface{})
+func (opts UpdateOpts) ToNetworkUpdateMap() (map[string]interface{}, error) {
+	n := make(map[string]interface{})
 
-	if o.AdminStateUp != nil {
-		inner["admin_state_up"] = &o.AdminStateUp
+	if opts.AdminStateUp != nil {
+		n["admin_state_up"] = &opts.AdminStateUp
 	}
-	if o.Name != "" {
-		inner["name"] = o.Name
+	if opts.Name != "" {
+		n["name"] = opts.Name
 	}
-	if o.Shared != nil {
-		inner["shared"] = &o.Shared
+	if opts.Shared != nil {
+		n["shared"] = &opts.Shared
 	}
 
-	outer := make(map[string]map[string]interface{})
-	outer["network"] = inner
-
-	return outer
+	return map[string]interface{}{"network": n}, nil
 }
 
 // Update accepts a UpdateOpts struct and updates an existing network using the
@@ -164,7 +181,11 @@
 func Update(c *gophercloud.ServiceClient, networkID string, opts UpdateOptsBuilder) UpdateResult {
 	var res UpdateResult
 
-	reqBody := opts.ToNetworkUpdateMap()
+	reqBody, err := opts.ToNetworkUpdateMap()
+	if err != nil {
+		res.Err = err
+		return res
+	}
 
 	// Send request to API
 	_, res.Err = perigee.Request("PUT", getURL(c, networkID), perigee.Options{
diff --git a/openstack/networking/v2/ports/requests.go b/openstack/networking/v2/ports/requests.go
index 0fbb9f2..c846de9 100644
--- a/openstack/networking/v2/ports/requests.go
+++ b/openstack/networking/v2/ports/requests.go
@@ -1,9 +1,10 @@
 package ports
 
 import (
-	"github.com/racker/perigee"
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
+
+	"github.com/racker/perigee"
 )
 
 // AdminState gives users a solid type to work with for create and update
@@ -19,6 +20,12 @@
 	Down AdminState = &iFalse
 )
 
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+	ToPortListQuery() (string, error)
+}
+
 // ListOpts allows the filtering and sorting of paginated collections through
 // the API. Filtering is achieved by passing in struct field values that map to
 // the port attributes you want to see returned. SortKey allows you to sort
@@ -40,6 +47,15 @@
 	SortDir      string `q:"sort_dir"`
 }
 
+// ToPortListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToPortListQuery() (string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	if err != nil {
+		return "", err
+	}
+	return q.String(), nil
+}
+
 // List returns a Pager which allows you to iterate over a collection of
 // ports. It accepts a ListOpts struct, which allows you to filter and sort
 // the returned collection for greater efficiency.
@@ -47,15 +63,17 @@
 // Default policy settings return only those ports that are owned by the tenant
 // who submits the request, unless the request is submitted by an user with
 // administrative rights.
-func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager {
-	// Build query parameters
-	q, err := gophercloud.BuildQueryString(&opts)
-	if err != nil {
-		return pagination.Pager{Err: err}
+func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+	url := listURL(c)
+	if opts != nil {
+		query, err := opts.ToPortListQuery()
+		if err != nil {
+			return pagination.Pager{Err: err}
+		}
+		url += query
 	}
-	u := listURL(c) + q.String()
 
-	return pagination.NewPager(c, u, func(r pagination.LastHTTPResponse) pagination.Page {
+	return pagination.NewPager(c, url, func(r pagination.LastHTTPResponse) pagination.Page {
 		return PortPage{pagination.LinkedPageBase{LastHTTPResponse: r}}
 	})
 }
@@ -71,6 +89,14 @@
 	return res
 }
 
+// CreateOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the main Create operation in this package. Since many
+// extensions decorate or modify the common logic, it is useful for them to
+// satisfy a basic interface in order for them to be used.
+type CreateOptsBuilder interface {
+	ToPortCreateMap() (map[string]interface{}, error)
+}
+
 // CreateOpts represents the attributes used when creating a new port.
 type CreateOpts struct {
 	NetworkID      string
@@ -84,51 +110,54 @@
 	SecurityGroups []string
 }
 
+// ToPortCreateMap casts a CreateOpts struct to a map.
+func (opts CreateOpts) ToPortCreateMap() (map[string]interface{}, error) {
+	p := make(map[string]interface{})
+
+	if opts.NetworkID == "" {
+		return nil, errNetworkIDRequired
+	}
+	p["network_id"] = opts.NetworkID
+
+	if opts.DeviceID != "" {
+		p["device_id"] = opts.DeviceID
+	}
+	if opts.DeviceOwner != "" {
+		p["device_owner"] = opts.DeviceOwner
+	}
+	if opts.FixedIPs != nil {
+		p["fixed_ips"] = opts.FixedIPs
+	}
+	if opts.SecurityGroups != nil {
+		p["security_groups"] = opts.SecurityGroups
+	}
+	if opts.TenantID != "" {
+		p["tenant_id"] = opts.TenantID
+	}
+	if opts.AdminStateUp != nil {
+		p["admin_state_up"] = &opts.AdminStateUp
+	}
+	if opts.Name != "" {
+		p["name"] = opts.Name
+	}
+	if opts.MACAddress != "" {
+		p["mac_address"] = opts.MACAddress
+	}
+
+	return map[string]interface{}{"port": p}, nil
+}
+
 // Create accepts a CreateOpts struct and creates a new network using the values
 // provided. You must remember to provide a NetworkID value.
-func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
+func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
 	var res CreateResult
 
-	type port struct {
-		NetworkID      string      `json:"network_id"`
-		Name           *string     `json:"name,omitempty"`
-		AdminStateUp   *bool       `json:"admin_state_up,omitempty"`
-		MACAddress     *string     `json:"mac_address,omitempty"`
-		FixedIPs       interface{} `json:"fixed_ips,omitempty"`
-		DeviceID       *string     `json:"device_id,omitempty"`
-		DeviceOwner    *string     `json:"device_owner,omitempty"`
-		TenantID       *string     `json:"tenant_id,omitempty"`
-		SecurityGroups []string    `json:"security_groups,omitempty"`
-	}
-	type request struct {
-		Port port `json:"port"`
-	}
-
-	// Validate
-	if opts.NetworkID == "" {
-		res.Err = errNetworkIDRequired
+	reqBody, err := opts.ToPortCreateMap()
+	if err != nil {
+		res.Err = err
 		return res
 	}
 
-	// Populate request body
-	reqBody := request{Port: port{
-		NetworkID:    opts.NetworkID,
-		Name:         gophercloud.MaybeString(opts.Name),
-		AdminStateUp: opts.AdminStateUp,
-		TenantID:     gophercloud.MaybeString(opts.TenantID),
-		MACAddress:   gophercloud.MaybeString(opts.MACAddress),
-		DeviceID:     gophercloud.MaybeString(opts.DeviceID),
-		DeviceOwner:  gophercloud.MaybeString(opts.DeviceOwner),
-	}}
-
-	if opts.FixedIPs != nil {
-		reqBody.Port.FixedIPs = opts.FixedIPs
-	}
-
-	if opts.SecurityGroups != nil {
-		reqBody.Port.SecurityGroups = opts.SecurityGroups
-	}
-
 	// Response
 	_, res.Err = perigee.Request("POST", createURL(c), perigee.Options{
 		MoreHeaders: c.Provider.AuthenticatedHeaders(),
@@ -141,6 +170,14 @@
 	return res
 }
 
+// UpdateOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the main Update operation in this package. Since many
+// extensions decorate or modify the common logic, it is useful for them to
+// satisfy a basic interface in order for them to be used.
+type UpdateOptsBuilder interface {
+	ToPortUpdateMap() (map[string]interface{}, error)
+}
+
 // UpdateOpts represents the attributes used when updating an existing port.
 type UpdateOpts struct {
 	Name           string
@@ -151,39 +188,43 @@
 	SecurityGroups []string
 }
 
+// ToPortUpdateMap casts an UpdateOpts struct to a map.
+func (opts UpdateOpts) ToPortUpdateMap() (map[string]interface{}, error) {
+	p := make(map[string]interface{})
+
+	if opts.DeviceID != "" {
+		p["device_id"] = opts.DeviceID
+	}
+	if opts.DeviceOwner != "" {
+		p["device_owner"] = opts.DeviceOwner
+	}
+	if opts.FixedIPs != nil {
+		p["fixed_ips"] = opts.FixedIPs
+	}
+	if opts.SecurityGroups != nil {
+		p["security_groups"] = opts.SecurityGroups
+	}
+	if opts.AdminStateUp != nil {
+		p["admin_state_up"] = &opts.AdminStateUp
+	}
+	if opts.Name != "" {
+		p["name"] = opts.Name
+	}
+
+	return map[string]interface{}{"port": p}, nil
+}
+
 // Update accepts a UpdateOpts struct and updates an existing port using the
 // values provided.
-func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult {
-	type port struct {
-		Name           *string     `json:"name,omitempty"`
-		AdminStateUp   *bool       `json:"admin_state_up,omitempty"`
-		FixedIPs       interface{} `json:"fixed_ips,omitempty"`
-		DeviceID       *string     `json:"device_id,omitempty"`
-		DeviceOwner    *string     `json:"device_owner,omitempty"`
-		SecurityGroups []string    `json:"security_groups,omitempty"`
-	}
-	type request struct {
-		Port port `json:"port"`
-	}
-
-	// Populate request body
-	reqBody := request{Port: port{
-		Name:         gophercloud.MaybeString(opts.Name),
-		AdminStateUp: opts.AdminStateUp,
-		DeviceID:     gophercloud.MaybeString(opts.DeviceID),
-		DeviceOwner:  gophercloud.MaybeString(opts.DeviceOwner),
-	}}
-
-	if opts.FixedIPs != nil {
-		reqBody.Port.FixedIPs = opts.FixedIPs
-	}
-
-	if opts.SecurityGroups != nil {
-		reqBody.Port.SecurityGroups = opts.SecurityGroups
-	}
-
-	// Response
+func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
 	var res UpdateResult
+
+	reqBody, err := opts.ToPortUpdateMap()
+	if err != nil {
+		res.Err = err
+		return res
+	}
+
 	_, res.Err = perigee.Request("PUT", updateURL(c, id), perigee.Options{
 		MoreHeaders: c.Provider.AuthenticatedHeaders(),
 		ReqBody:     &reqBody,
diff --git a/openstack/networking/v2/subnets/requests.go b/openstack/networking/v2/subnets/requests.go
index a7e6b53..8eed269 100644
--- a/openstack/networking/v2/subnets/requests.go
+++ b/openstack/networking/v2/subnets/requests.go
@@ -1,9 +1,10 @@
 package subnets
 
 import (
-	"github.com/racker/perigee"
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
+
+	"github.com/racker/perigee"
 )
 
 // AdminState gives users a solid type to work with for create and update
@@ -19,6 +20,12 @@
 	Down AdminState = &iFalse
 )
 
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+	ToSubnetListQuery() (string, error)
+}
+
 // ListOpts allows the filtering and sorting of paginated collections through
 // the API. Filtering is achieved by passing in struct field values that map to
 // the subnet attributes you want to see returned. SortKey allows you to sort
@@ -39,6 +46,15 @@
 	SortDir    string `q:"sort_dir"`
 }
 
+// ToSubnetListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToSubnetListQuery() (string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	if err != nil {
+		return "", err
+	}
+	return q.String(), nil
+}
+
 // List returns a Pager which allows you to iterate over a collection of
 // subnets. It accepts a ListOpts struct, which allows you to filter and sort
 // the returned collection for greater efficiency.
@@ -46,13 +62,15 @@
 // Default policy settings return only those subnets that are owned by the tenant
 // who submits the request, unless the request is submitted by an user with
 // administrative rights.
-func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager {
-	// Build query parameters
-	query, err := gophercloud.BuildQueryString(&opts)
-	if err != nil {
-		return pagination.Pager{Err: err}
+func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+	url := listURL(c)
+	if opts != nil {
+		query, err := opts.ToSubnetListQuery()
+		if err != nil {
+			return pagination.Pager{Err: err}
+		}
+		url += query
 	}
-	url := listURL(c) + query.String()
 
 	return pagination.NewPager(c, url, func(r pagination.LastHTTPResponse) pagination.Page {
 		return SubnetPage{pagination.LinkedPageBase{LastHTTPResponse: r}}
@@ -76,6 +94,14 @@
 	IPv6 = 6
 )
 
+// CreateOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the main Create operation in this package. Since many
+// extensions decorate or modify the common logic, it is useful for them to
+// satisfy a basic interface in order for them to be used.
+type CreateOptsBuilder interface {
+	ToSubnetCreateMap() (map[string]interface{}, error)
+}
+
 // CreateOpts represents the attributes used when creating a new subnet.
 type CreateOpts struct {
 	// Required
@@ -92,61 +118,60 @@
 	HostRoutes      []HostRoute
 }
 
-// Create accepts a CreateOpts struct and creates a new subnet using the values
-// provided. You must remember to provide a valid NetworkID, CIDR and IP version.
-func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
-	var res CreateResult
+// ToSubnetCreateMap casts a CreateOpts struct to a map.
+func (opts CreateOpts) ToSubnetCreateMap() (map[string]interface{}, error) {
+	s := make(map[string]interface{})
 
-	// Validate required options
 	if opts.NetworkID == "" {
-		res.Err = errNetworkIDRequired
-		return res
+		return nil, errNetworkIDRequired
 	}
 	if opts.CIDR == "" {
-		res.Err = errCIDRRequired
-		return res
+		return nil, errCIDRRequired
 	}
 	if opts.IPVersion != 0 && opts.IPVersion != IPv4 && opts.IPVersion != IPv6 {
-		res.Err = errInvalidIPType
-		return res
+		return nil, errInvalidIPType
 	}
 
-	type subnet struct {
-		NetworkID       string           `json:"network_id"`
-		CIDR            string           `json:"cidr"`
-		Name            *string          `json:"name,omitempty"`
-		TenantID        *string          `json:"tenant_id,omitempty"`
-		AllocationPools []AllocationPool `json:"allocation_pools,omitempty"`
-		GatewayIP       *string          `json:"gateway_ip,omitempty"`
-		IPVersion       int              `json:"ip_version,omitempty"`
-		EnableDHCP      *bool            `json:"enable_dhcp,omitempty"`
-		DNSNameservers  []string         `json:"dns_nameservers,omitempty"`
-		HostRoutes      []HostRoute      `json:"host_routes,omitempty"`
-	}
-	type request struct {
-		Subnet subnet `json:"subnet"`
-	}
+	s["network_id"] = opts.NetworkID
+	s["cidr"] = opts.CIDR
 
-	reqBody := request{Subnet: subnet{
-		NetworkID:  opts.NetworkID,
-		CIDR:       opts.CIDR,
-		Name:       gophercloud.MaybeString(opts.Name),
-		TenantID:   gophercloud.MaybeString(opts.TenantID),
-		GatewayIP:  gophercloud.MaybeString(opts.GatewayIP),
-		EnableDHCP: opts.EnableDHCP,
-	}}
-
+	if opts.EnableDHCP != nil {
+		s["enable_dhcp"] = &opts.EnableDHCP
+	}
+	if opts.Name != "" {
+		s["name"] = opts.Name
+	}
+	if opts.GatewayIP != "" {
+		s["gateway_ip"] = opts.GatewayIP
+	}
+	if opts.TenantID != "" {
+		s["tenant_id"] = opts.TenantID
+	}
 	if opts.IPVersion != 0 {
-		reqBody.Subnet.IPVersion = opts.IPVersion
+		s["ip_version"] = opts.IPVersion
 	}
 	if len(opts.AllocationPools) != 0 {
-		reqBody.Subnet.AllocationPools = opts.AllocationPools
+		s["allocation_pools"] = opts.AllocationPools
 	}
 	if len(opts.DNSNameservers) != 0 {
-		reqBody.Subnet.DNSNameservers = opts.DNSNameservers
+		s["dns_nameservers"] = opts.DNSNameservers
 	}
 	if len(opts.HostRoutes) != 0 {
-		reqBody.Subnet.HostRoutes = opts.HostRoutes
+		s["host_routes"] = opts.HostRoutes
+	}
+
+	return map[string]interface{}{"subnet": s}, nil
+}
+
+// Create accepts a CreateOpts struct and creates a new subnet using the values
+// provided. You must remember to provide a valid NetworkID, CIDR and IP version.
+func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
+	var res CreateResult
+
+	reqBody, err := opts.ToSubnetCreateMap()
+	if err != nil {
+		res.Err = err
+		return res
 	}
 
 	_, res.Err = perigee.Request("POST", createURL(c), perigee.Options{
@@ -159,6 +184,12 @@
 	return res
 }
 
+// UpdateOptsBuilder allows extensions to add additional parameters to the
+// Update request.
+type UpdateOptsBuilder interface {
+	ToSubnetUpdateMap() (map[string]interface{}, error)
+}
+
 // UpdateOpts represents the attributes used when updating an existing subnet.
 type UpdateOpts struct {
 	Name           string
@@ -168,35 +199,40 @@
 	EnableDHCP     *bool
 }
 
+// ToSubnetUpdateMap casts an UpdateOpts struct to a map.
+func (opts UpdateOpts) ToSubnetUpdateMap() (map[string]interface{}, error) {
+	s := make(map[string]interface{})
+
+	if opts.EnableDHCP != nil {
+		s["enable_dhcp"] = &opts.EnableDHCP
+	}
+	if opts.Name != "" {
+		s["name"] = opts.Name
+	}
+	if opts.GatewayIP != "" {
+		s["gateway_ip"] = opts.GatewayIP
+	}
+	if len(opts.DNSNameservers) != 0 {
+		s["dns_nameservers"] = opts.DNSNameservers
+	}
+	if len(opts.HostRoutes) != 0 {
+		s["host_routes"] = opts.HostRoutes
+	}
+
+	return map[string]interface{}{"subnet": s}, nil
+}
+
 // Update accepts a UpdateOpts struct and updates an existing subnet using the
 // values provided.
-func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult {
-	type subnet struct {
-		Name           *string     `json:"name,omitempty"`
-		GatewayIP      *string     `json:"gateway_ip,omitempty"`
-		DNSNameservers []string    `json:"dns_nameservers,omitempty"`
-		HostRoutes     []HostRoute `json:"host_routes,omitempty"`
-		EnableDHCP     *bool       `json:"enable_dhcp,omitempty"`
-	}
-	type request struct {
-		Subnet subnet `json:"subnet"`
-	}
-
-	reqBody := request{Subnet: subnet{
-		Name:       gophercloud.MaybeString(opts.Name),
-		GatewayIP:  gophercloud.MaybeString(opts.GatewayIP),
-		EnableDHCP: opts.EnableDHCP,
-	}}
-
-	if len(opts.DNSNameservers) != 0 {
-		reqBody.Subnet.DNSNameservers = opts.DNSNameservers
-	}
-
-	if len(opts.HostRoutes) != 0 {
-		reqBody.Subnet.HostRoutes = opts.HostRoutes
-	}
-
+func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
 	var res UpdateResult
+
+	reqBody, err := opts.ToSubnetUpdateMap()
+	if err != nil {
+		res.Err = err
+		return res
+	}
+
 	_, res.Err = perigee.Request("PUT", updateURL(c, id), perigee.Options{
 		MoreHeaders: c.Provider.AuthenticatedHeaders(),
 		ReqBody:     &reqBody,
diff --git a/openstack/objectstorage/v1/accounts/requests.go b/openstack/objectstorage/v1/accounts/requests.go
index 5de09da..55fcb05 100644
--- a/openstack/objectstorage/v1/accounts/requests.go
+++ b/openstack/objectstorage/v1/accounts/requests.go
@@ -5,63 +5,103 @@
 	"github.com/rackspace/gophercloud"
 )
 
-// UpdateOpts is a structure that contains parameters for updating, creating, or deleting an
-// account's metadata.
-type UpdateOpts struct {
-	Metadata map[string]string
-	Headers  map[string]string
+// GetOptsBuilder allows extensions to add additional headers to the Get
+// request.
+type GetOptsBuilder interface {
+	ToAccountGetMap() (map[string]string, error)
 }
 
-// Update is a function that creates, updates, or deletes an account's metadata.
-func Update(c *gophercloud.ServiceClient, opts UpdateOpts) UpdateResult {
-	headers := c.Provider.AuthenticatedHeaders()
+// GetOpts is a structure that contains parameters for getting an account's
+// metadata.
+type GetOpts struct {
+	Newest bool `h:"X-Newest"`
+}
 
-	for k, v := range opts.Headers {
-		headers[k] = v
+// ToAccountGetMap formats a GetOpts into a map[string]string of headers.
+func (opts GetOpts) ToAccountGetMap() (map[string]string, error) {
+	return gophercloud.BuildHeaders(opts)
+}
+
+// Get is a function that retrieves an account's metadata. To extract just the
+// custom metadata, call the ExtractMetadata method on the GetResult. To extract
+// all the headers that are returned (including the metadata), call the
+// ExtractHeaders method on the GetResult.
+func Get(c *gophercloud.ServiceClient, opts GetOptsBuilder) GetResult {
+	var res GetResult
+	h := c.Provider.AuthenticatedHeaders()
+
+	if opts != nil {
+		headers, err := opts.ToAccountGetMap()
+		if err != nil {
+			res.Err = err
+			return res
+		}
+
+		for k, v := range headers {
+			h[k] = v
+		}
 	}
 
-	for k, v := range opts.Metadata {
-		headers["X-Account-Meta-"+k] = v
-	}
-
-	var res UpdateResult
-
-	var resp *perigee.Response
-
-	resp, res.Err = perigee.Request("POST", accountURL(c), perigee.Options{
-		MoreHeaders: headers,
+	resp, err := perigee.Request("HEAD", getURL(c), perigee.Options{
+		MoreHeaders: h,
 		OkCodes:     []int{204},
 	})
-
 	res.Resp = &resp.HttpResponse
-
+	res.Err = err
 	return res
 }
 
-// GetOpts is a structure that contains parameters for getting an account's metadata.
-type GetOpts struct {
-	Headers map[string]string
+// UpdateOptsBuilder allows extensions to add additional headers to the Update
+// request.
+type UpdateOptsBuilder interface {
+	ToAccountUpdateMap() (map[string]string, error)
 }
 
-// Get is a function that retrieves an account's metadata. To extract just the custom
-// metadata, pass the GetResult response to the ExtractMetadata function.
-func Get(c *gophercloud.ServiceClient, opts GetOpts) GetResult {
-	headers := c.Provider.AuthenticatedHeaders()
+// UpdateOpts is a structure that contains parameters for updating, creating, or
+// deleting an account's metadata.
+type UpdateOpts struct {
+	Metadata          map[string]string
+	ContentType       string `h:"Content-Type"`
+	DetectContentType bool   `h:"X-Detect-Content-Type"`
+	TempURLKey        string `h:"X-Account-Meta-Temp-URL-Key"`
+	TempURLKey2       string `h:"X-Account-Meta-Temp-URL-Key-2"`
+}
 
-	for k, v := range opts.Headers {
-		headers[k] = v
+// ToAccountUpdateMap formats an UpdateOpts into a map[string]string of headers.
+func (opts UpdateOpts) ToAccountUpdateMap() (map[string]string, error) {
+	headers, err := gophercloud.BuildHeaders(opts)
+	if err != nil {
+		return nil, err
+	}
+	for k, v := range opts.Metadata {
+		headers["X-Account-Meta-"+k] = v
+	}
+	return headers, err
+}
+
+// Update is a function that creates, updates, or deletes an account's metadata.
+// To extract the headers returned, call the ExtractHeaders method on the
+// UpdateResult.
+func Update(c *gophercloud.ServiceClient, opts UpdateOptsBuilder) UpdateResult {
+	var res UpdateResult
+	h := c.Provider.AuthenticatedHeaders()
+
+	if opts != nil {
+		headers, err := opts.ToAccountUpdateMap()
+		if err != nil {
+			res.Err = err
+			return res
+		}
+		for k, v := range headers {
+			h[k] = v
+		}
 	}
 
-	var res GetResult
-	var resp *perigee.Response
-
-	resp, res.Err = perigee.Request("HEAD", accountURL(c), perigee.Options{
-		MoreHeaders: headers,
-		Results:     &res.Resp,
+	resp, err := perigee.Request("POST", updateURL(c), perigee.Options{
+		MoreHeaders: h,
 		OkCodes:     []int{204},
 	})
-
 	res.Resp = &resp.HttpResponse
-
+	res.Err = err
 	return res
 }
diff --git a/openstack/objectstorage/v1/accounts/requests_test.go b/openstack/objectstorage/v1/accounts/requests_test.go
index 8cb2be8..0090eea 100644
--- a/openstack/objectstorage/v1/accounts/requests_test.go
+++ b/openstack/objectstorage/v1/accounts/requests_test.go
@@ -4,20 +4,20 @@
 	"net/http"
 	"testing"
 
-	"github.com/rackspace/gophercloud/testhelper"
+	th "github.com/rackspace/gophercloud/testhelper"
 	fake "github.com/rackspace/gophercloud/testhelper/client"
 )
 
 var metadata = map[string]string{"gophercloud-test": "accounts"}
 
 func TestUpdateAccount(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
-	testhelper.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "POST")
-		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		testhelper.TestHeader(t, r, "X-Account-Meta-Gophercloud-Test", "accounts")
+	th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "X-Account-Meta-Gophercloud-Test", "accounts")
 
 		w.Header().Set("X-Account-Container-Count", "2")
 		w.Header().Set("X-Account-Bytes-Used", "14")
@@ -26,35 +26,28 @@
 		w.WriteHeader(http.StatusNoContent)
 	})
 
-	res := Update(fake.ServiceClient(), UpdateOpts{Metadata: metadata})
-
-	metadata := res.ExtractMetadata()
-	expected := map[string]string{"Subject": "books"}
-
-	testhelper.AssertDeepEquals(t, expected, metadata)
-	testhelper.AssertNoErr(t, res.Err)
+	options := &UpdateOpts{Metadata: map[string]string{"gophercloud-test": "accounts"}}
+	_, err := Update(fake.ServiceClient(), options).ExtractHeaders()
+	if err != nil {
+		t.Fatalf("Unable to update account: %v", err)
+	}
 }
 
 func TestGetAccount(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
-	testhelper.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "HEAD")
-		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-
-		w.Header().Set("X-Account-Container-Count", "2")
-		w.Header().Set("X-Account-Bytes-Used", "14")
-		w.Header().Set("X-Account-Meta-Subject", "books")
-
+	th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "HEAD")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		w.Header().Set("X-Account-Meta-Foo", "bar")
 		w.WriteHeader(http.StatusNoContent)
 	})
 
-	res := Get(fake.ServiceClient(), GetOpts{})
-
-	metadata := res.ExtractMetadata()
-	expected := map[string]string{"Subject": "books"}
-
-	testhelper.AssertDeepEquals(t, expected, metadata)
-	testhelper.AssertNoErr(t, res.Err)
+	expected := map[string]string{"Foo": "bar"}
+	actual, err := Get(fake.ServiceClient(), &GetOpts{}).ExtractMetadata()
+	if err != nil {
+		t.Fatalf("Unable to get account metadata: %v", err)
+	}
+	th.CheckDeepEquals(t, expected, actual)
 }
diff --git a/openstack/objectstorage/v1/accounts/results.go b/openstack/objectstorage/v1/accounts/results.go
index 8ec2eff..8ff8183 100644
--- a/openstack/objectstorage/v1/accounts/results.go
+++ b/openstack/objectstorage/v1/accounts/results.go
@@ -1,38 +1,34 @@
 package accounts
 
 import (
-	"net/http"
 	"strings"
 
-	"github.com/rackspace/gophercloud"
+	objectstorage "github.com/rackspace/gophercloud/openstack/objectstorage/v1"
 )
 
-type commonResult struct {
-	gophercloud.CommonResult
-	Resp *http.Response
-}
-
-// GetResult represents the result of a create operation.
+// GetResult is returned from a call to the Get function. See v1.CommonResult.
 type GetResult struct {
-	commonResult
-}
-
-// UpdateResult represents the result of an update operation.
-type UpdateResult struct {
-	commonResult
+	objectstorage.CommonResult
 }
 
 // ExtractMetadata is a function that takes a GetResult (of type *http.Response)
 // and returns the custom metatdata associated with the account.
-func (res commonResult) ExtractMetadata() map[string]string {
-	metadata := make(map[string]string)
+func (gr GetResult) ExtractMetadata() (map[string]string, error) {
+	if gr.Err != nil {
+		return nil, gr.Err
+	}
 
-	for k, v := range res.Resp.Header {
+	metadata := make(map[string]string)
+	for k, v := range gr.Resp.Header {
 		if strings.HasPrefix(k, "X-Account-Meta-") {
 			key := strings.TrimPrefix(k, "X-Account-Meta-")
 			metadata[key] = v[0]
 		}
 	}
+	return metadata, nil
+}
 
-	return metadata
+// UpdateResult is returned from a call to the Update function. See v1.CommonResult.
+type UpdateResult struct {
+	objectstorage.CommonResult
 }
diff --git a/openstack/objectstorage/v1/accounts/urls.go b/openstack/objectstorage/v1/accounts/urls.go
index 53b1343..9952fe4 100644
--- a/openstack/objectstorage/v1/accounts/urls.go
+++ b/openstack/objectstorage/v1/accounts/urls.go
@@ -2,7 +2,10 @@
 
 import "github.com/rackspace/gophercloud"
 
-// accountURL returns the URI for making Account requests.
-func accountURL(c *gophercloud.ServiceClient) string {
+func getURL(c *gophercloud.ServiceClient) string {
 	return c.Endpoint
 }
+
+func updateURL(c *gophercloud.ServiceClient) string {
+	return getURL(c)
+}
diff --git a/openstack/objectstorage/v1/accounts/urls_test.go b/openstack/objectstorage/v1/accounts/urls_test.go
index f127a5e..074d52d 100644
--- a/openstack/objectstorage/v1/accounts/urls_test.go
+++ b/openstack/objectstorage/v1/accounts/urls_test.go
@@ -4,14 +4,23 @@
 	"testing"
 
 	"github.com/rackspace/gophercloud"
+	th "github.com/rackspace/gophercloud/testhelper"
 )
 
-func TestAccountURL(t *testing.T) {
-	client := gophercloud.ServiceClient{
-		Endpoint: "http://localhost:5000/v3/",
-	}
-	url := accountURL(&client)
-	if url != "http://localhost:5000/v3/" {
-		t.Errorf("Unexpected service URL generated: [%s]", url)
-	}
+const endpoint = "http://localhost:57909/"
+
+func endpointClient() *gophercloud.ServiceClient {
+	return &gophercloud.ServiceClient{Endpoint: endpoint}
+}
+
+func TestGetURL(t *testing.T) {
+	actual := getURL(endpointClient())
+	expected := endpoint
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestUpdateURL(t *testing.T) {
+	actual := updateURL(endpointClient())
+	expected := endpoint
+	th.CheckEquals(t, expected, actual)
 }
diff --git a/openstack/objectstorage/v1/common.go b/openstack/objectstorage/v1/common.go
new file mode 100644
index 0000000..1a6c44a
--- /dev/null
+++ b/openstack/objectstorage/v1/common.go
@@ -0,0 +1,25 @@
+package v1
+
+import (
+	"net/http"
+)
+
+// CommonResult is a structure that contains the response and error of a call to an
+// object storage endpoint.
+type CommonResult struct {
+	Resp *http.Response
+	Err  error
+}
+
+// ExtractHeaders will extract and return the headers from a *http.Response.
+func (cr CommonResult) ExtractHeaders() (http.Header, error) {
+	if cr.Err != nil {
+		return nil, cr.Err
+	}
+
+	var headers http.Header
+	if cr.Err != nil {
+		return headers, cr.Err
+	}
+	return cr.Resp.Header, nil
+}
diff --git a/openstack/objectstorage/v1/containers/requests.go b/openstack/objectstorage/v1/containers/requests.go
index d772a43..ce3f540 100644
--- a/openstack/objectstorage/v1/containers/requests.go
+++ b/openstack/objectstorage/v1/containers/requests.go
@@ -6,35 +6,50 @@
 	"github.com/rackspace/gophercloud/pagination"
 )
 
+// ListOptsBuilder allows extensions to add additional parameters to the List
+// request.
+type ListOptsBuilder interface {
+	ToContainerListParams() (bool, string, error)
+}
+
 // ListOpts is a structure that holds options for listing containers.
 type ListOpts struct {
 	Full      bool
-	Limit     int     `q:"limit"`
-	Marker    string  `q:"marker"`
-	EndMarker string  `q:"end_marker"`
-	Format    string  `q:"format"`
-	Prefix    string  `q:"prefix"`
-	Delimiter [1]byte `q:"delimiter"`
+	Limit     int    `q:"limit"`
+	Marker    string `q:"marker"`
+	EndMarker string `q:"end_marker"`
+	Format    string `q:"format"`
+	Prefix    string `q:"prefix"`
+	Delimiter string `q:"delimiter"`
 }
 
-// List is a function that retrieves containers associated with the account as well as account
-// metadata. It returns a pager which can be iterated with the EachPage function.
-func List(c *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager {
-	var headers map[string]string
+// ToContainerListParams formats a ListOpts into a query string and boolean
+// representing whether to list complete information for each container.
+func (opts ListOpts) ToContainerListParams() (bool, string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	if err != nil {
+		return false, "", err
+	}
+	return opts.Full, q.String(), nil
+}
 
-	url := accountURL(c)
+// List is a function that retrieves containers associated with the account as
+// well as account metadata. It returns a pager which can be iterated with the
+// EachPage function.
+func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+	headers := map[string]string{"Accept": "text/plain", "Content-Type": "text/plain"}
+
+	url := listURL(c)
 	if opts != nil {
-		query, err := gophercloud.BuildQueryString(opts)
+		full, query, err := opts.ToContainerListParams()
 		if err != nil {
 			return pagination.Pager{Err: err}
 		}
-		url += query.String()
+		url += query
 
-		if !opts.Full {
-			headers = map[string]string{"Accept": "text/plain", "Content-Type": "text/plain"}
+		if full {
+			headers = map[string]string{"Accept": "application/json", "Content-Type": "application/json"}
 		}
-	} else {
-		headers = map[string]string{"Accept": "text/plain", "Content-Type": "text/plain"}
 	}
 
 	createPage := func(r pagination.LastHTTPResponse) pagination.Page {
@@ -48,6 +63,12 @@
 	return pager
 }
 
+// CreateOptsBuilder allows extensions to add additional parameters to the
+// Create request.
+type CreateOptsBuilder interface {
+	ToContainerCreateMap() (map[string]string, error)
+}
+
 // CreateOpts is a structure that holds parameters for creating a container.
 type CreateOpts struct {
 	Metadata          map[string]string
@@ -61,13 +82,25 @@
 	VersionsLocation  string `h:"X-Versions-Location"`
 }
 
+// ToContainerCreateMap formats a CreateOpts into a map of headers.
+func (opts CreateOpts) ToContainerCreateMap() (map[string]string, error) {
+	h, err := gophercloud.BuildHeaders(opts)
+	if err != nil {
+		return nil, err
+	}
+	for k, v := range opts.Metadata {
+		h["X-Container-Meta-"+k] = v
+	}
+	return h, nil
+}
+
 // Create is a function that creates a new container.
-func Create(c *gophercloud.ServiceClient, containerName string, opts *CreateOpts) CreateResult {
+func Create(c *gophercloud.ServiceClient, containerName string, opts CreateOptsBuilder) CreateResult {
 	var res CreateResult
 	h := c.Provider.AuthenticatedHeaders()
 
 	if opts != nil {
-		headers, err := gophercloud.BuildHeaders(opts)
+		headers, err := opts.ToContainerCreateMap()
 		if err != nil {
 			res.Err = err
 			return res
@@ -76,13 +109,9 @@
 		for k, v := range headers {
 			h[k] = v
 		}
-
-		for k, v := range opts.Metadata {
-			h["X-Container-Meta-"+k] = v
-		}
 	}
 
-	resp, err := perigee.Request("PUT", containerURL(c, containerName), perigee.Options{
+	resp, err := perigee.Request("PUT", createURL(c, containerName), perigee.Options{
 		MoreHeaders: h,
 		OkCodes:     []int{201, 204},
 	})
@@ -94,7 +123,7 @@
 // Delete is a function that deletes a container.
 func Delete(c *gophercloud.ServiceClient, containerName string) DeleteResult {
 	var res DeleteResult
-	resp, err := perigee.Request("DELETE", containerURL(c, containerName), perigee.Options{
+	resp, err := perigee.Request("DELETE", deleteURL(c, containerName), perigee.Options{
 		MoreHeaders: c.Provider.AuthenticatedHeaders(),
 		OkCodes:     []int{204},
 	})
@@ -103,8 +132,14 @@
 	return res
 }
 
-// UpdateOpts is a structure that holds parameters for updating, creating, or deleting a
-// container's metadata.
+// UpdateOptsBuilder allows extensions to add additional parameters to the
+// Update request.
+type UpdateOptsBuilder interface {
+	ToContainerUpdateMap() (map[string]string, error)
+}
+
+// UpdateOpts is a structure that holds parameters for updating, creating, or
+// deleting a container's metadata.
 type UpdateOpts struct {
 	Metadata               map[string]string
 	ContainerRead          string `h:"X-Container-Read"`
@@ -117,13 +152,26 @@
 	VersionsLocation       string `h:"X-Versions-Location"`
 }
 
-// Update is a function that creates, updates, or deletes a container's metadata.
-func Update(c *gophercloud.ServiceClient, containerName string, opts *UpdateOpts) UpdateResult {
+// ToContainerUpdateMap formats a CreateOpts into a map of headers.
+func (opts UpdateOpts) ToContainerUpdateMap() (map[string]string, error) {
+	h, err := gophercloud.BuildHeaders(opts)
+	if err != nil {
+		return nil, err
+	}
+	for k, v := range opts.Metadata {
+		h["X-Container-Meta-"+k] = v
+	}
+	return h, nil
+}
+
+// Update is a function that creates, updates, or deletes a container's
+// metadata.
+func Update(c *gophercloud.ServiceClient, containerName string, opts UpdateOptsBuilder) UpdateResult {
 	var res UpdateResult
 	h := c.Provider.AuthenticatedHeaders()
 
 	if opts != nil {
-		headers, err := gophercloud.BuildHeaders(opts)
+		headers, err := opts.ToContainerUpdateMap()
 		if err != nil {
 			res.Err = err
 			return res
@@ -132,13 +180,9 @@
 		for k, v := range headers {
 			h[k] = v
 		}
-
-		for k, v := range opts.Metadata {
-			h["X-Container-Meta-"+k] = v
-		}
 	}
 
-	resp, err := perigee.Request("POST", containerURL(c, containerName), perigee.Options{
+	resp, err := perigee.Request("POST", updateURL(c, containerName), perigee.Options{
 		MoreHeaders: h,
 		OkCodes:     []int{204},
 	})
@@ -147,11 +191,12 @@
 	return res
 }
 
-// Get is a function that retrieves the metadata of a container. To extract just the custom
-// metadata, pass the GetResult response to the ExtractMetadata function.
+// Get is a function that retrieves the metadata of a container. To extract just
+// the custom metadata, pass the GetResult response to the ExtractMetadata
+// function.
 func Get(c *gophercloud.ServiceClient, containerName string) GetResult {
 	var res GetResult
-	resp, err := perigee.Request("HEAD", containerURL(c, containerName), perigee.Options{
+	resp, err := perigee.Request("HEAD", getURL(c, containerName), perigee.Options{
 		MoreHeaders: c.Provider.AuthenticatedHeaders(),
 		OkCodes:     []int{204},
 	})
diff --git a/openstack/objectstorage/v1/containers/requests_test.go b/openstack/objectstorage/v1/containers/requests_test.go
index 09930d0..9562676 100644
--- a/openstack/objectstorage/v1/containers/requests_test.go
+++ b/openstack/objectstorage/v1/containers/requests_test.go
@@ -6,20 +6,20 @@
 	"testing"
 
 	"github.com/rackspace/gophercloud/pagination"
-	"github.com/rackspace/gophercloud/testhelper"
+	th "github.com/rackspace/gophercloud/testhelper"
 	fake "github.com/rackspace/gophercloud/testhelper/client"
 )
 
 var metadata = map[string]string{"gophercloud-test": "containers"}
 
 func TestListContainerInfo(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
-	testhelper.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "GET")
-		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		testhelper.TestHeader(t, r, "Accept", "application/json")
+	th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
 
 		w.Header().Set("Content-Type", "application/json")
 		r.ParseForm()
@@ -68,7 +68,7 @@
 			},
 		}
 
-		testhelper.CheckDeepEquals(t, expected, actual)
+		th.CheckDeepEquals(t, expected, actual)
 
 		return true, nil
 	})
@@ -79,13 +79,13 @@
 }
 
 func TestListContainerNames(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
-	testhelper.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "GET")
-		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		testhelper.TestHeader(t, r, "Accept", "text/plain")
+	th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "text/plain")
 
 		w.Header().Set("Content-Type", "text/plain")
 		r.ParseForm()
@@ -112,7 +112,7 @@
 
 		expected := []string{"janeausten", "marktwain"}
 
-		testhelper.CheckDeepEquals(t, expected, actual)
+		th.CheckDeepEquals(t, expected, actual)
 
 		return true, nil
 	})
@@ -123,30 +123,34 @@
 }
 
 func TestCreateContainer(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
-	testhelper.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "PUT")
-		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		testhelper.TestHeader(t, r, "Accept", "application/json")
+	th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "PUT")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
+
+		w.Header().Add("X-Container-Meta-Foo", "bar")
 		w.WriteHeader(http.StatusNoContent)
 	})
 
-	_, err := Create(fake.ServiceClient(), "testContainer", nil).ExtractHeaders()
+	options := CreateOpts{ContentType: "application/json", Metadata: map[string]string{"foo": "bar"}}
+	headers, err := Create(fake.ServiceClient(), "testContainer", options).ExtractHeaders()
 	if err != nil {
 		t.Fatalf("Unexpected error creating container: %v", err)
 	}
+	th.CheckEquals(t, "bar", headers["X-Container-Meta-Foo"][0])
 }
 
 func TestDeleteContainer(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
-	testhelper.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "DELETE")
-		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		testhelper.TestHeader(t, r, "Accept", "application/json")
+	th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "DELETE")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
 		w.WriteHeader(http.StatusNoContent)
 	})
 
@@ -157,30 +161,31 @@
 }
 
 func TestUpateContainer(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
-	testhelper.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "POST")
-		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		testhelper.TestHeader(t, r, "Accept", "application/json")
+	th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
 		w.WriteHeader(http.StatusNoContent)
 	})
 
-	_, err := Update(fake.ServiceClient(), "testContainer", nil).ExtractHeaders()
+	options := &UpdateOpts{Metadata: map[string]string{"foo": "bar"}}
+	_, err := Update(fake.ServiceClient(), "testContainer", options).ExtractHeaders()
 	if err != nil {
 		t.Fatalf("Unexpected error updating container metadata: %v", err)
 	}
 }
 
 func TestGetContainer(t *testing.T) {
-	testhelper.SetupHTTP()
-	defer testhelper.TeardownHTTP()
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
 
-	testhelper.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
-		testhelper.TestMethod(t, r, "HEAD")
-		testhelper.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
-		testhelper.TestHeader(t, r, "Accept", "application/json")
+	th.Mux.HandleFunc("/testContainer", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "HEAD")
+		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+		th.TestHeader(t, r, "Accept", "application/json")
 		w.WriteHeader(http.StatusNoContent)
 	})
 
diff --git a/openstack/objectstorage/v1/containers/results.go b/openstack/objectstorage/v1/containers/results.go
index be96fca..227a9dc 100644
--- a/openstack/objectstorage/v1/containers/results.go
+++ b/openstack/objectstorage/v1/containers/results.go
@@ -2,11 +2,12 @@
 
 import (
 	"fmt"
-	"net/http"
 	"strings"
 
-	"github.com/mitchellh/mapstructure"
+	objectstorage "github.com/rackspace/gophercloud/openstack/objectstorage/v1"
 	"github.com/rackspace/gophercloud/pagination"
+
+	"github.com/mitchellh/mapstructure"
 )
 
 // Container represents a container resource.
@@ -97,8 +98,7 @@
 
 // GetResult represents the result of a get operation.
 type GetResult struct {
-	Resp *http.Response
-	Err  error
+	objectstorage.CommonResult
 }
 
 // ExtractMetadata is a function that takes a GetResult (of type *http.Response)
@@ -117,37 +117,23 @@
 	return metadata, nil
 }
 
-type commonResult struct {
-	Resp *http.Response
-	Err  error
-}
-
-func (cr commonResult) ExtractHeaders() (http.Header, error) {
-	var headers http.Header
-	if cr.Err != nil {
-		return headers, cr.Err
-	}
-
-	return cr.Resp.Header, nil
-}
-
 // CreateResult represents the result of a create operation. To extract the
 // the headers from the HTTP response, you can invoke the 'ExtractHeaders'
 // method on the result struct.
 type CreateResult struct {
-	commonResult
+	objectstorage.CommonResult
 }
 
 // UpdateResult represents the result of an update operation. To extract the
 // the headers from the HTTP response, you can invoke the 'ExtractHeaders'
 // method on the result struct.
 type UpdateResult struct {
-	commonResult
+	objectstorage.CommonResult
 }
 
 // DeleteResult represents the result of a delete operation. To extract the
 // the headers from the HTTP response, you can invoke the 'ExtractHeaders'
 // method on the result struct.
 type DeleteResult struct {
-	commonResult
+	objectstorage.CommonResult
 }
diff --git a/openstack/objectstorage/v1/containers/urls.go b/openstack/objectstorage/v1/containers/urls.go
index 2a06f95..f864f84 100644
--- a/openstack/objectstorage/v1/containers/urls.go
+++ b/openstack/objectstorage/v1/containers/urls.go
@@ -2,12 +2,22 @@
 
 import "github.com/rackspace/gophercloud"
 
-// accountURL returns the URI used to list Containers.
-func accountURL(c *gophercloud.ServiceClient) string {
+func listURL(c *gophercloud.ServiceClient) string {
 	return c.Endpoint
 }
 
-// containerURL returns the URI for making Container requests.
-func containerURL(c *gophercloud.ServiceClient, container string) string {
+func createURL(c *gophercloud.ServiceClient, container string) string {
 	return c.ServiceURL(container)
 }
+
+func getURL(c *gophercloud.ServiceClient, container string) string {
+	return createURL(c, container)
+}
+
+func deleteURL(c *gophercloud.ServiceClient, container string) string {
+	return createURL(c, container)
+}
+
+func updateURL(c *gophercloud.ServiceClient, container string) string {
+	return createURL(c, container)
+}
diff --git a/openstack/objectstorage/v1/containers/urls_test.go b/openstack/objectstorage/v1/containers/urls_test.go
index da37bf6..d043a2a 100644
--- a/openstack/objectstorage/v1/containers/urls_test.go
+++ b/openstack/objectstorage/v1/containers/urls_test.go
@@ -1,29 +1,43 @@
 package containers
 
 import (
-	"testing"
 	"github.com/rackspace/gophercloud"
+	th "github.com/rackspace/gophercloud/testhelper"
+	"testing"
 )
 
-func TestAccountURL(t *testing.T) {
-	client := gophercloud.ServiceClient{
-		Endpoint: "http://localhost:5000/v1/",
-	}
-	expected := "http://localhost:5000/v1/"
-	actual := accountURL(&client)
-	if actual != expected {
-		t.Errorf("Unexpected service URL generated: [%s]", actual)
-	}
+const endpoint = "http://localhost:57909/"
 
+func endpointClient() *gophercloud.ServiceClient {
+	return &gophercloud.ServiceClient{Endpoint: endpoint}
 }
 
-func TestContainerURL(t *testing.T) {
-	client := gophercloud.ServiceClient{
-		Endpoint: "http://localhost:5000/v1/",
-	}
-	expected := "http://localhost:5000/v1/testContainer"
-	actual := containerURL(&client, "testContainer")
-	if actual != expected {
-		t.Errorf("Unexpected service URL generated: [%s]", actual)
-	}
+func TestListURL(t *testing.T) {
+	actual := listURL(endpointClient())
+	expected := endpoint
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestCreateURL(t *testing.T) {
+	actual := createURL(endpointClient(), "foo")
+	expected := endpoint + "foo"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestGetURL(t *testing.T) {
+	actual := getURL(endpointClient(), "foo")
+	expected := endpoint + "foo"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestDeleteURL(t *testing.T) {
+	actual := deleteURL(endpointClient(), "foo")
+	expected := endpoint + "foo"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestUpdateURL(t *testing.T) {
+	actual := updateURL(endpointClient(), "foo")
+	expected := endpoint + "foo"
+	th.CheckEquals(t, expected, actual)
 }
diff --git a/openstack/objectstorage/v1/objects/requests.go b/openstack/objectstorage/v1/objects/requests.go
index bc21496..3274e04 100644
--- a/openstack/objectstorage/v1/objects/requests.go
+++ b/openstack/objectstorage/v1/objects/requests.go
@@ -10,38 +10,52 @@
 	"github.com/rackspace/gophercloud/pagination"
 )
 
+// ListOptsBuilder allows extensions to add additional parameters to the List
+// request.
+type ListOptsBuilder interface {
+	ToObjectListParams() (bool, string, error)
+}
+
 // ListOpts is a structure that holds parameters for listing objects.
 type ListOpts struct {
 	Full      bool
-	Limit     int     `q:"limit"`
-	Marker    string  `q:"marker"`
-	EndMarker string  `q:"end_marker"`
-	Format    string  `q:"format"`
-	Prefix    string  `q:"prefix"`
-	Delimiter [1]byte `q:"delimiter"`
-	Path      string  `q:"path"`
+	Limit     int    `q:"limit"`
+	Marker    string `q:"marker"`
+	EndMarker string `q:"end_marker"`
+	Format    string `q:"format"`
+	Prefix    string `q:"prefix"`
+	Delimiter string `q:"delimiter"`
+	Path      string `q:"path"`
+}
+
+// ToObjectListParams formats a ListOpts into a query string and boolean
+// representing whether to list complete information for each object.
+func (opts ListOpts) ToObjectListParams() (bool, string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	if err != nil {
+		return false, "", err
+	}
+	return opts.Full, q.String(), nil
 }
 
 // List is a function that retrieves all objects in a container. It also returns the details
 // for the container. To extract only the object information or names, pass the ListResult
 // response to the ExtractInfo or ExtractNames function, respectively.
-func List(c *gophercloud.ServiceClient, containerName string, opts *ListOpts) pagination.Pager {
-	var headers map[string]string
+func List(c *gophercloud.ServiceClient, containerName string, opts ListOptsBuilder) pagination.Pager {
+	headers := map[string]string{"Accept": "text/plain", "Content-Type": "text/plain"}
 
-	url := containerURL(c, containerName)
+	url := listURL(c, containerName)
 	if opts != nil {
-		query, err := gophercloud.BuildQueryString(opts)
+		full, query, err := opts.ToObjectListParams()
 		if err != nil {
 			fmt.Printf("Error building query string: %v", err)
 			return pagination.Pager{Err: err}
 		}
-		url += query.String()
+		url += query
 
-		if !opts.Full {
-			headers = map[string]string{"Accept": "text/plain", "Content-Type": "text/plain"}
+		if full {
+			headers = map[string]string{"Accept": "application/json", "Content-Type": "application/json"}
 		}
-	} else {
-		headers = map[string]string{"Accept": "text/plain", "Content-Type": "text/plain"}
 	}
 
 	createPage := func(r pagination.LastHTTPResponse) pagination.Page {
@@ -55,6 +69,12 @@
 	return pager
 }
 
+// DownloadOptsBuilder allows extensions to add additional parameters to the
+// Download request.
+type DownloadOptsBuilder interface {
+	ToObjectDownloadParams() (map[string]string, string, error)
+}
+
 // DownloadOpts is a structure that holds parameters for downloading an object.
 type DownloadOpts struct {
 	IfMatch           string    `h:"If-Match"`
@@ -67,17 +87,31 @@
 	Signature         string    `q:"signature"`
 }
 
+// ToObjectDownloadParams formats a DownloadOpts into a query string and map of
+// headers.
+func (opts ListOpts) ToObjectDownloadParams() (map[string]string, string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	if err != nil {
+		return nil, "", err
+	}
+	h, err := gophercloud.BuildHeaders(opts)
+	if err != nil {
+		return nil, q.String(), err
+	}
+	return h, q.String(), nil
+}
+
 // Download is a function that retrieves the content and metadata for an object.
-// To extract just the content, pass the DownloadResult response to the ExtractContent
-// function.
-func Download(c *gophercloud.ServiceClient, containerName, objectName string, opts *DownloadOpts) DownloadResult {
+// To extract just the content, pass the DownloadResult response to the
+// ExtractContent function.
+func Download(c *gophercloud.ServiceClient, containerName, objectName string, opts DownloadOptsBuilder) DownloadResult {
 	var res DownloadResult
 
-	url := objectURL(c, containerName, objectName)
+	url := downloadURL(c, containerName, objectName)
 	h := c.Provider.AuthenticatedHeaders()
 
 	if opts != nil {
-		headers, err := gophercloud.BuildHeaders(opts)
+		headers, query, err := opts.ToObjectDownloadParams()
 		if err != nil {
 			res.Err = err
 			return res
@@ -87,12 +121,7 @@
 			h[k] = v
 		}
 
-		query, err := gophercloud.BuildQueryString(opts)
-		if err != nil {
-			res.Err = err
-			return res
-		}
-		url += query.String()
+		url += query
 	}
 
 	resp, err := perigee.Request("GET", url, perigee.Options{
@@ -104,6 +133,12 @@
 	return res
 }
 
+// CreateOptsBuilder allows extensions to add additional parameters to the
+// Create request.
+type CreateOptsBuilder interface {
+	ToObjectCreateParams() (map[string]string, string, error)
+}
+
 // CreateOpts is a structure that holds parameters for creating an object.
 type CreateOpts struct {
 	Metadata           map[string]string
@@ -124,16 +159,35 @@
 	Signature          string `q:"signature"`
 }
 
+// ToObjectCreateParams formats a CreateOpts into a query string and map of
+// headers.
+func (opts CreateOpts) ToObjectCreateParams() (map[string]string, string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	if err != nil {
+		return nil, "", err
+	}
+	h, err := gophercloud.BuildHeaders(opts)
+	if err != nil {
+		return nil, q.String(), err
+	}
+
+	for k, v := range opts.Metadata {
+		h["X-Object-Meta-"+k] = v
+	}
+
+	return h, q.String(), nil
+}
+
 // Create is a function that creates a new object or replaces an existing object.
-func Create(c *gophercloud.ServiceClient, containerName, objectName string, content io.Reader, opts *CreateOpts) CreateResult {
+func Create(c *gophercloud.ServiceClient, containerName, objectName string, content io.Reader, opts CreateOptsBuilder) CreateResult {
 	var res CreateResult
 	var reqBody []byte
 
-	url := objectURL(c, containerName, objectName)
+	url := createURL(c, containerName, objectName)
 	h := c.Provider.AuthenticatedHeaders()
 
 	if opts != nil {
-		headers, err := gophercloud.BuildHeaders(opts)
+		headers, query, err := opts.ToObjectCreateParams()
 		if err != nil {
 			res.Err = err
 			return res
@@ -143,17 +197,7 @@
 			h[k] = v
 		}
 
-		for k, v := range opts.Metadata {
-			h["X-Object-Meta-"+k] = v
-		}
-
-		query, err := gophercloud.BuildQueryString(opts)
-		if err != nil {
-			res.Err = err
-			return res
-		}
-
-		url += query.String()
+		url += query
 	}
 
 	if content != nil {
@@ -175,7 +219,14 @@
 	return res
 }
 
-// CopyOpts is a structure that holds parameters for copying one object to another.
+// CopyOptsBuilder allows extensions to add additional parameters to the
+// Copy request.
+type CopyOptsBuilder interface {
+	ToObjectCopyMap() (map[string]string, error)
+}
+
+// CopyOpts is a structure that holds parameters for copying one object to
+// another.
 type CopyOpts struct {
 	Metadata           map[string]string
 	ContentDisposition string `h:"Content-Disposition"`
@@ -184,29 +235,37 @@
 	Destination        string `h:"Destination,required"`
 }
 
+// ToObjectCopyMap formats a CopyOpts into a map of headers.
+func (opts CopyOpts) ToObjectCopyMap() (map[string]string, error) {
+	if opts.Destination == "" {
+		return nil, fmt.Errorf("Required CopyOpts field 'Destination' not set.")
+	}
+	h, err := gophercloud.BuildHeaders(opts)
+	if err != nil {
+		return nil, err
+	}
+	for k, v := range opts.Metadata {
+		h["X-Object-Meta-"+k] = v
+	}
+	return h, nil
+}
+
 // Copy is a function that copies one object to another.
-func Copy(c *gophercloud.ServiceClient, containerName, objectName string, opts *CopyOpts) CopyResult {
+func Copy(c *gophercloud.ServiceClient, containerName, objectName string, opts CopyOptsBuilder) CopyResult {
 	var res CopyResult
 	h := c.Provider.AuthenticatedHeaders()
 
-	if opts == nil {
-		res.Err = fmt.Errorf("Required CopyOpts field 'Destination' not set.")
-		return res
-	}
-	headers, err := gophercloud.BuildHeaders(opts)
+	headers, err := opts.ToObjectCopyMap()
 	if err != nil {
 		res.Err = err
 		return res
 	}
+
 	for k, v := range headers {
 		h[k] = v
 	}
 
-	for k, v := range opts.Metadata {
-		h["X-Object-Meta-"+k] = v
-	}
-
-	url := objectURL(c, containerName, objectName)
+	url := copyURL(c, containerName, objectName)
 	resp, err := perigee.Request("COPY", url, perigee.Options{
 		MoreHeaders: h,
 		OkCodes:     []int{201},
@@ -215,23 +274,38 @@
 	return res
 }
 
+// DeleteOptsBuilder allows extensions to add additional parameters to the
+// Delete request.
+type DeleteOptsBuilder interface {
+	ToObjectDeleteQuery() (string, error)
+}
+
 // DeleteOpts is a structure that holds parameters for deleting an object.
 type DeleteOpts struct {
 	MultipartManifest string `q:"multipart-manifest"`
 }
 
+// ToObjectDeleteQuery formats a DeleteOpts into a query string.
+func (opts DeleteOpts) ToObjectDeleteQuery() (string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	if err != nil {
+		return "", err
+	}
+	return q.String(), nil
+}
+
 // Delete is a function that deletes an object.
-func Delete(c *gophercloud.ServiceClient, containerName, objectName string, opts *DeleteOpts) DeleteResult {
+func Delete(c *gophercloud.ServiceClient, containerName, objectName string, opts DeleteOptsBuilder) DeleteResult {
 	var res DeleteResult
-	url := objectURL(c, containerName, objectName)
+	url := deleteURL(c, containerName, objectName)
 
 	if opts != nil {
-		query, err := gophercloud.BuildQueryString(opts)
+		query, err := opts.ToObjectDeleteQuery()
 		if err != nil {
 			res.Err = err
 			return res
 		}
-		url += query.String()
+		url += query
 	}
 
 	resp, err := perigee.Request("DELETE", url, perigee.Options{
@@ -243,25 +317,40 @@
 	return res
 }
 
+// GetOptsBuilder allows extensions to add additional parameters to the
+// Get request.
+type GetOptsBuilder interface {
+	ToObjectGetQuery() (string, error)
+}
+
 // GetOpts is a structure that holds parameters for getting an object's metadata.
 type GetOpts struct {
 	Expires   string `q:"expires"`
 	Signature string `q:"signature"`
 }
 
+// ToObjectGetQuery formats a GetOpts into a query string.
+func (opts GetOpts) ToObjectGetQuery() (string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	if err != nil {
+		return "", err
+	}
+	return q.String(), nil
+}
+
 // Get is a function that retrieves the metadata of an object. To extract just the custom
 // metadata, pass the GetResult response to the ExtractMetadata function.
-func Get(c *gophercloud.ServiceClient, containerName, objectName string, opts *GetOpts) GetResult {
+func Get(c *gophercloud.ServiceClient, containerName, objectName string, opts GetOptsBuilder) GetResult {
 	var res GetResult
-	url := objectURL(c, containerName, objectName)
+	url := getURL(c, containerName, objectName)
 
 	if opts != nil {
-		query, err := gophercloud.BuildQueryString(opts)
+		query, err := opts.ToObjectGetQuery()
 		if err != nil {
 			res.Err = err
 			return res
 		}
-		url += query.String()
+		url += query
 	}
 
 	resp, err := perigee.Request("HEAD", url, perigee.Options{
@@ -273,6 +362,12 @@
 	return res
 }
 
+// UpdateOptsBuilder allows extensions to add additional parameters to the
+// Update request.
+type UpdateOptsBuilder interface {
+	ToObjectUpdateMap() (map[string]string, error)
+}
+
 // UpdateOpts is a structure that holds parameters for updating, creating, or deleting an
 // object's metadata.
 type UpdateOpts struct {
@@ -285,13 +380,25 @@
 	DetectContentType  bool   `h:"X-Detect-Content-Type"`
 }
 
+// ToObjectUpdateMap formats a UpdateOpts into a map of headers.
+func (opts UpdateOpts) ToObjectUpdateMap() (map[string]string, error) {
+	h, err := gophercloud.BuildHeaders(opts)
+	if err != nil {
+		return nil, err
+	}
+	for k, v := range opts.Metadata {
+		h["X-Object-Meta-"+k] = v
+	}
+	return h, nil
+}
+
 // Update is a function that creates, updates, or deletes an object's metadata.
-func Update(c *gophercloud.ServiceClient, containerName, objectName string, opts *UpdateOpts) UpdateResult {
+func Update(c *gophercloud.ServiceClient, containerName, objectName string, opts UpdateOptsBuilder) UpdateResult {
 	var res UpdateResult
 	h := c.Provider.AuthenticatedHeaders()
 
 	if opts != nil {
-		headers, err := gophercloud.BuildHeaders(opts)
+		headers, err := opts.ToObjectUpdateMap()
 		if err != nil {
 			res.Err = err
 			return res
@@ -300,13 +407,9 @@
 		for k, v := range headers {
 			h[k] = v
 		}
-
-		for k, v := range opts.Metadata {
-			h["X-Object-Meta-"+k] = v
-		}
 	}
 
-	url := objectURL(c, containerName, objectName)
+	url := updateURL(c, containerName, objectName)
 	resp, err := perigee.Request("POST", url, perigee.Options{
 		MoreHeaders: h,
 		OkCodes:     []int{202},
diff --git a/openstack/objectstorage/v1/objects/requests_test.go b/openstack/objectstorage/v1/objects/requests_test.go
index 089081f..11d7c44 100644
--- a/openstack/objectstorage/v1/objects/requests_test.go
+++ b/openstack/objectstorage/v1/objects/requests_test.go
@@ -166,7 +166,8 @@
 	})
 
 	content := bytes.NewBufferString("Did gyre and gimble in the wabe")
-	_, err := Create(fake.ServiceClient(), "testContainer", "testObject", content, nil).ExtractHeaders()
+	options := &CreateOpts{ContentType: "application/json"}
+	_, err := Create(fake.ServiceClient(), "testContainer", "testObject", content, options).ExtractHeaders()
 	if err != nil {
 		t.Fatalf("Unexpected error creating object: %v", err)
 	}
@@ -184,7 +185,8 @@
 		w.WriteHeader(http.StatusCreated)
 	})
 
-	_, err := Copy(fake.ServiceClient(), "testContainer", "testObject", &CopyOpts{Destination: "/newTestContainer/newTestObject"}).ExtractHeaders()
+	options := &CopyOpts{Destination: "/newTestContainer/newTestObject"}
+	_, err := Copy(fake.ServiceClient(), "testContainer", "testObject", options).ExtractHeaders()
 	if err != nil {
 		t.Fatalf("Unexpected error copying object: %v", err)
 	}
diff --git a/openstack/objectstorage/v1/objects/results.go b/openstack/objectstorage/v1/objects/results.go
index f7db3ed..1dda7a3 100644
--- a/openstack/objectstorage/v1/objects/results.go
+++ b/openstack/objectstorage/v1/objects/results.go
@@ -3,11 +3,12 @@
 import (
 	"fmt"
 	"io/ioutil"
-	"net/http"
 	"strings"
 
-	"github.com/mitchellh/mapstructure"
+	objectstorage "github.com/rackspace/gophercloud/openstack/objectstorage/v1"
 	"github.com/rackspace/gophercloud/pagination"
+
+	"github.com/mitchellh/mapstructure"
 )
 
 // Object is a structure that holds information related to a storage object.
@@ -97,7 +98,7 @@
 
 // DownloadResult is a *http.Response that is returned from a call to the Download function.
 type DownloadResult struct {
-	commonResult
+	objectstorage.CommonResult
 }
 
 // ExtractContent is a function that takes a DownloadResult (of type *http.Response)
@@ -117,7 +118,7 @@
 
 // GetResult is a *http.Response that is returned from a call to the Get function.
 type GetResult struct {
-	commonResult
+	objectstorage.CommonResult
 }
 
 // ExtractMetadata is a function that takes a GetResult (of type *http.Response)
@@ -136,36 +137,22 @@
 	return metadata, nil
 }
 
-type commonResult struct {
-	Resp *http.Response
-	Err  error
-}
-
-func (cr commonResult) ExtractHeaders() (http.Header, error) {
-	var headers http.Header
-	if cr.Err != nil {
-		return headers, cr.Err
-	}
-
-	return cr.Resp.Header, nil
-}
-
 // CreateResult represents the result of a create operation.
 type CreateResult struct {
-	commonResult
+	objectstorage.CommonResult
 }
 
 // UpdateResult represents the result of an update operation.
 type UpdateResult struct {
-	commonResult
+	objectstorage.CommonResult
 }
 
 // DeleteResult represents the result of a delete operation.
 type DeleteResult struct {
-	commonResult
+	objectstorage.CommonResult
 }
 
 // CopyResult represents the result of a copy operation.
 type CopyResult struct {
-	commonResult
+	objectstorage.CommonResult
 }
diff --git a/openstack/objectstorage/v1/objects/urls.go b/openstack/objectstorage/v1/objects/urls.go
index a377960..d2ec62c 100644
--- a/openstack/objectstorage/v1/objects/urls.go
+++ b/openstack/objectstorage/v1/objects/urls.go
@@ -1,13 +1,33 @@
 package objects
 
-import "github.com/rackspace/gophercloud"
+import (
+	"github.com/rackspace/gophercloud"
+)
 
-// objectURL returns the URI for making Object requests.
-func objectURL(c *gophercloud.ServiceClient, container, object string) string {
+func listURL(c *gophercloud.ServiceClient, container string) string {
+	return c.ServiceURL(container)
+}
+
+func copyURL(c *gophercloud.ServiceClient, container, object string) string {
 	return c.ServiceURL(container, object)
 }
 
-// containerURL returns the URI for making Container requests.
-func containerURL(c *gophercloud.ServiceClient, container string) string {
-	return c.ServiceURL(container)
+func createURL(c *gophercloud.ServiceClient, container, object string) string {
+	return copyURL(c, container, object)
+}
+
+func getURL(c *gophercloud.ServiceClient, container, object string) string {
+	return copyURL(c, container, object)
+}
+
+func deleteURL(c *gophercloud.ServiceClient, container, object string) string {
+	return copyURL(c, container, object)
+}
+
+func downloadURL(c *gophercloud.ServiceClient, container, object string) string {
+	return copyURL(c, container, object)
+}
+
+func updateURL(c *gophercloud.ServiceClient, container, object string) string {
+	return copyURL(c, container, object)
 }
diff --git a/openstack/objectstorage/v1/objects/urls_test.go b/openstack/objectstorage/v1/objects/urls_test.go
index 89d1cb1..1dcfe35 100644
--- a/openstack/objectstorage/v1/objects/urls_test.go
+++ b/openstack/objectstorage/v1/objects/urls_test.go
@@ -2,27 +2,55 @@
 
 import (
 	"testing"
+
 	"github.com/rackspace/gophercloud"
+	th "github.com/rackspace/gophercloud/testhelper"
 )
 
-func TestContainerURL(t *testing.T) {
-	client := gophercloud.ServiceClient{
-		Endpoint: "http://localhost:5000/v1/",
-	}
-	expected := "http://localhost:5000/v1/testContainer"
-	actual := containerURL(&client, "testContainer")
-	if actual != expected {
-		t.Errorf("Unexpected service URL generated: %s", actual)
-	}
+const endpoint = "http://localhost:57909/"
+
+func endpointClient() *gophercloud.ServiceClient {
+	return &gophercloud.ServiceClient{Endpoint: endpoint}
 }
 
-func TestObjectURL(t *testing.T) {
-	client := gophercloud.ServiceClient{
-		Endpoint: "http://localhost:5000/v1/",
-	}
-	expected := "http://localhost:5000/v1/testContainer/testObject"
-	actual := objectURL(&client, "testContainer", "testObject")
-	if actual != expected {
-		t.Errorf("Unexpected service URL generated: %s", actual)
-	}
+func TestListURL(t *testing.T) {
+	actual := listURL(endpointClient(), "foo")
+	expected := endpoint + "foo"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestCopyURL(t *testing.T) {
+	actual := copyURL(endpointClient(), "foo", "bar")
+	expected := endpoint + "foo/bar"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestCreateURL(t *testing.T) {
+	actual := createURL(endpointClient(), "foo", "bar")
+	expected := endpoint + "foo/bar"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestGetURL(t *testing.T) {
+	actual := getURL(endpointClient(), "foo", "bar")
+	expected := endpoint + "foo/bar"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestDeleteURL(t *testing.T) {
+	actual := deleteURL(endpointClient(), "foo", "bar")
+	expected := endpoint + "foo/bar"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestDownloadURL(t *testing.T) {
+	actual := downloadURL(endpointClient(), "foo", "bar")
+	expected := endpoint + "foo/bar"
+	th.CheckEquals(t, expected, actual)
+}
+
+func TestUpdateURL(t *testing.T) {
+	actual := updateURL(endpointClient(), "foo", "bar")
+	expected := endpoint + "foo/bar"
+	th.CheckEquals(t, expected, actual)
 }
diff --git a/params_test.go b/params_test.go
index 03eaefc..9f1d3bd 100644
--- a/params_test.go
+++ b/params_test.go
@@ -2,58 +2,141 @@
 
 import (
 	"net/url"
+	"reflect"
 	"testing"
+	"time"
 
 	th "github.com/rackspace/gophercloud/testhelper"
 )
 
-func TestMaybeStringWithNonEmptyString(t *testing.T) {
-	testString := "carol"
-	expected := &testString
-	actual := MaybeString("carol")
-	th.CheckDeepEquals(t, actual, expected)
-}
-
-func TestMaybeStringWithEmptyString(t *testing.T) {
+func TestMaybeString(t *testing.T) {
+	testString := ""
 	var expected *string
-	actual := MaybeString("")
-	th.CheckDeepEquals(t, actual, expected)
+	actual := MaybeString(testString)
+	th.CheckDeepEquals(t, expected, actual)
+
+	testString = "carol"
+	expected = &testString
+	actual = MaybeString(testString)
+	th.CheckDeepEquals(t, expected, actual)
 }
 
-func TestBuildQueryStringWithPointerToStruct(t *testing.T) {
-	expected := &url.URL{
-		RawQuery: "j=2&r=red",
-	}
+func TestMaybeInt(t *testing.T) {
+	testInt := 0
+	var expected *int
+	actual := MaybeInt(testInt)
+	th.CheckDeepEquals(t, expected, actual)
 
-	type Opts struct {
+	testInt = 4
+	expected = &testInt
+	actual = MaybeInt(testInt)
+	th.CheckDeepEquals(t, expected, actual)
+}
+
+func TestBuildQueryString(t *testing.T) {
+	opts := struct {
 		J int    `q:"j"`
-		R string `q:"r"`
-		C bool
+		R string `q:"r,required"`
+		C bool   `q:"c"`
+	}{
+		J: 2,
+		R: "red",
+		C: true,
 	}
-
-	opts := Opts{J: 2, R: "red"}
-
+	expected := &url.URL{RawQuery: "j=2&r=red&c=true"}
 	actual, err := BuildQueryString(&opts)
 	if err != nil {
 		t.Errorf("Error building query string: %v", err)
 	}
+	th.CheckDeepEquals(t, expected, actual)
 
-	th.CheckDeepEquals(t, actual, expected)
-}
-
-func TestBuildQueryStringWithoutRequiredFieldSet(t *testing.T) {
-	type Opts struct {
+	opts = struct {
 		J int    `q:"j"`
 		R string `q:"r,required"`
-		C bool
+		C bool   `q:"c"`
+	}{
+		J: 2,
+		C: true,
 	}
-
-	opts := Opts{J: 2, C: true}
-
-	_, err := BuildQueryString(&opts)
+	_, err = BuildQueryString(&opts)
 	if err == nil {
-		t.Error("Unexpected result: There should be an error thrown when a required field isn't set.")
+		t.Errorf("Expected error: 'Required field not set'")
+	}
+	th.CheckDeepEquals(t, expected, actual)
+
+	_, err = BuildQueryString(map[string]interface{}{"Number": 4})
+	if err == nil {
+		t.Errorf("Expected error: 'Options type is not a struct'")
+	}
+}
+
+func TestBuildHeaders(t *testing.T) {
+	testStruct := struct {
+		Accept string `h:"Accept"`
+		Num    int    `h:"Number,required"`
+		Style  bool   `h:"Style"`
+	}{
+		Accept: "application/json",
+		Num:    4,
+		Style:  true,
+	}
+	expected := map[string]string{"Accept": "application/json", "Number": "4", "Style": "true"}
+	actual, err := BuildHeaders(&testStruct)
+	th.CheckNoErr(t, err)
+	th.CheckDeepEquals(t, expected, actual)
+
+	testStruct.Num = 0
+	_, err = BuildHeaders(&testStruct)
+	if err == nil {
+		t.Errorf("Expected error: 'Required header not set'")
 	}
 
-	t.Log(err)
+	_, err = BuildHeaders(map[string]interface{}{"Number": 4})
+	if err == nil {
+		t.Errorf("Expected error: 'Options type is not a struct'")
+	}
+}
+
+func TestIsZero(t *testing.T) {
+	var testMap map[string]interface{}
+	testMapValue := reflect.ValueOf(testMap)
+	expected := true
+	actual := isZero(testMapValue)
+	th.CheckEquals(t, expected, actual)
+	testMap = map[string]interface{}{"empty": false}
+	testMapValue = reflect.ValueOf(testMap)
+	expected = false
+	actual = isZero(testMapValue)
+	th.CheckEquals(t, expected, actual)
+
+	var testArray [2]string
+	testArrayValue := reflect.ValueOf(testArray)
+	expected = true
+	actual = isZero(testArrayValue)
+	th.CheckEquals(t, expected, actual)
+	testArray = [2]string{"one", "two"}
+	testArrayValue = reflect.ValueOf(testArray)
+	expected = false
+	actual = isZero(testArrayValue)
+	th.CheckEquals(t, expected, actual)
+
+	var testStruct struct {
+		A string
+		B time.Time
+	}
+	testStructValue := reflect.ValueOf(testStruct)
+	expected = true
+	actual = isZero(testStructValue)
+	th.CheckEquals(t, expected, actual)
+	testStruct = struct {
+		A string
+		B time.Time
+	}{
+		B: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
+	}
+	testStructValue = reflect.ValueOf(testStruct)
+	expected = false
+	actual = isZero(testStructValue)
+	th.CheckEquals(t, expected, actual)
+
 }
diff --git a/provider_client_test.go b/provider_client_test.go
new file mode 100644
index 0000000..b260246
--- /dev/null
+++ b/provider_client_test.go
@@ -0,0 +1,16 @@
+package gophercloud
+
+import (
+	"testing"
+
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestAuthenticatedHeaders(t *testing.T) {
+	p := &ProviderClient{
+		TokenID: "1234",
+	}
+	expected := map[string]string{"X-Auth-Token": "1234"}
+	actual := p.AuthenticatedHeaders()
+	th.CheckDeepEquals(t, expected, actual)
+}
diff --git a/service_client_test.go b/service_client_test.go
new file mode 100644
index 0000000..84beb3f
--- /dev/null
+++ b/service_client_test.go
@@ -0,0 +1,14 @@
+package gophercloud
+
+import (
+	"testing"
+
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestServiceURL(t *testing.T) {
+	c := &ServiceClient{Endpoint: "http://123.45.67.8/"}
+	expected := "http://123.45.67.8/more/parts/here"
+	actual := c.ServiceURL("more", "parts", "here")
+	th.CheckEquals(t, expected, actual)
+}
diff --git a/util_test.go b/util_test.go
new file mode 100644
index 0000000..6fbd920
--- /dev/null
+++ b/util_test.go
@@ -0,0 +1,21 @@
+package gophercloud
+
+import (
+	"testing"
+
+	th "github.com/rackspace/gophercloud/testhelper"
+)
+
+func TestWaitFor(t *testing.T) {
+	err := WaitFor(0, func() (bool, error) {
+		return true, nil
+	})
+	if err == nil {
+		t.Errorf("Expected error: 'Time out in WaitFor'")
+	}
+
+	err = WaitFor(5, func() (bool, error) {
+		return true, nil
+	})
+	th.CheckNoErr(t, err)
+}