Merge pull request #231 from jamiehannaford/test-coverage-bump

Test coverage bump
diff --git a/acceptance/openstack/networking/v2/extensions/layer3_test.go b/acceptance/openstack/networking/v2/extensions/layer3_test.go
index 1289113..63e0be3 100644
--- a/acceptance/openstack/networking/v2/extensions/layer3_test.go
+++ b/acceptance/openstack/networking/v2/extensions/layer3_test.go
@@ -219,7 +219,7 @@
 }
 
 func updateRouter(t *testing.T, routerID string) {
-	r, err := routers.Update(base.Client, routerID, routers.UpdateOpts{
+	_, err := routers.Update(base.Client, routerID, routers.UpdateOpts{
 		Name: "another_name",
 	}).Extract()
 
diff --git a/acceptance/openstack/networking/v2/extensions/provider_test.go b/acceptance/openstack/networking/v2/extensions/provider_test.go
index dc21ae5..f10c9d9 100644
--- a/acceptance/openstack/networking/v2/extensions/provider_test.go
+++ b/acceptance/openstack/networking/v2/extensions/provider_test.go
@@ -6,24 +6,25 @@
 	"strconv"
 	"testing"
 
+	base "github.com/rackspace/gophercloud/acceptance/openstack/networking/v2"
 	"github.com/rackspace/gophercloud/openstack/networking/v2/networks"
 	"github.com/rackspace/gophercloud/pagination"
 	th "github.com/rackspace/gophercloud/testhelper"
 )
 
 func TestNetworkCRUDOperations(t *testing.T) {
-	Setup(t)
-	defer Teardown()
+	base.Setup(t)
+	defer base.Teardown()
 
 	// Create a network
-	n, err := networks.Create(Client, networks.CreateOpts{Name: "sample_network", AdminStateUp: true}).Extract()
+	n, err := networks.Create(base.Client, networks.CreateOpts{Name: "sample_network", AdminStateUp: networks.Up}).Extract()
 	th.AssertNoErr(t, err)
 	th.AssertEquals(t, n.Name, "sample_network")
 	th.AssertEquals(t, n.AdminStateUp, true)
 	networkID := n.ID
 
 	// List networks
-	pager := networks.List(Client, networks.ListOpts{Limit: 2})
+	pager := networks.List(base.Client, networks.ListOpts{Limit: 2})
 	err = pager.EachPage(func(page pagination.Page) (bool, error) {
 		t.Logf("--- Page ---")
 
@@ -43,7 +44,7 @@
 	if networkID == "" {
 		t.Fatalf("In order to retrieve a network, the NetworkID must be set")
 	}
-	n, err = networks.Get(Client, networkID).Extract()
+	n, err = networks.Get(base.Client, networkID).Extract()
 	th.AssertNoErr(t, err)
 	th.AssertEquals(t, n.Status, "ACTIVE")
 	th.AssertDeepEquals(t, n.Subnets, []string{})
@@ -53,12 +54,12 @@
 	th.AssertEquals(t, n.ID, networkID)
 
 	// Update network
-	n, err = networks.Update(Client, networkID, networks.UpdateOpts{Name: "new_network_name"}).Extract()
+	n, err = networks.Update(base.Client, networkID, networks.UpdateOpts{Name: "new_network_name"}).Extract()
 	th.AssertNoErr(t, err)
 	th.AssertEquals(t, n.Name, "new_network_name")
 
 	// Delete network
-	res := networks.Delete(Client, networkID)
+	res := networks.Delete(base.Client, networkID)
 	th.AssertNoErr(t, res.Err)
 }
 
diff --git a/acceptance/openstack/networking/v2/extensions/security_test.go b/acceptance/openstack/networking/v2/extensions/security_test.go
index 522219a..16ecca4 100644
--- a/acceptance/openstack/networking/v2/extensions/security_test.go
+++ b/acceptance/openstack/networking/v2/extensions/security_test.go
@@ -120,18 +120,6 @@
 	t.Logf("Deleted security group %s", groupID)
 }
 
-func deletePort(t *testing.T, portID string) {
-	res := ports.Delete(base.Client, portID)
-	th.AssertNoErr(t, res.Err)
-	t.Logf("Deleted port %s", portID)
-}
-
-func deleteNetwork(t *testing.T, networkID string) {
-	res := networks.Delete(base.Client, networkID)
-	th.AssertNoErr(t, res.Err)
-	t.Logf("Deleted network %s", networkID)
-}
-
 func createSecRule(t *testing.T, groupID string) string {
 	r, err := rules.Create(base.Client, rules.CreateOpts{
 		Direction:    "ingress",
diff --git a/acceptance/openstack/networking/v2/network_test.go b/acceptance/openstack/networking/v2/network_test.go
index f00edba..be8a3a1 100644
--- a/acceptance/openstack/networking/v2/network_test.go
+++ b/acceptance/openstack/networking/v2/network_test.go
@@ -16,7 +16,7 @@
 	defer Teardown()
 
 	// Create a network
-	n, err := networks.Create(Client, networks.CreateOpts{Name: "sample_network", AdminStateUp: true}).Extract()
+	n, err := networks.Create(Client, networks.CreateOpts{Name: "sample_network", AdminStateUp: networks.Up}).Extract()
 	th.AssertNoErr(t, err)
 	defer networks.Delete(Client, n.ID)
 	th.AssertEquals(t, n.Name, "sample_network")
diff --git a/acceptance/openstack/networking/v2/port_test.go b/acceptance/openstack/networking/v2/port_test.go
index 363a7fe..7f22dbd 100644
--- a/acceptance/openstack/networking/v2/port_test.go
+++ b/acceptance/openstack/networking/v2/port_test.go
@@ -97,18 +97,17 @@
 }
 
 func createNetwork() (string, error) {
-	res, err := networks.Create(Client, networks.CreateOpts{Name: "tmp_network", AdminStateUp: true}).Extract()
+	res, err := networks.Create(Client, networks.CreateOpts{Name: "tmp_network", AdminStateUp: networks.Up}).Extract()
 	return res.ID, err
 }
 
 func createSubnet(networkID string) (string, error) {
-	enable := false
 	s, err := subnets.Create(Client, subnets.CreateOpts{
 		NetworkID:  networkID,
 		CIDR:       "192.168.199.0/24",
 		IPVersion:  subnets.IPv4,
 		Name:       "my_subnet",
-		EnableDHCP: &enable,
+		EnableDHCP: subnets.Down,
 	}).Extract()
 	return s.ID, err
 }
diff --git a/acceptance/openstack/networking/v2/subnet_test.go b/acceptance/openstack/networking/v2/subnet_test.go
index 2758e2a..097a303 100644
--- a/acceptance/openstack/networking/v2/subnet_test.go
+++ b/acceptance/openstack/networking/v2/subnet_test.go
@@ -38,7 +38,7 @@
 
 	// Setup network
 	t.Log("Setting up network")
-	n, err := networks.Create(Client, networks.CreateOpts{Name: "tmp_network", AdminStateUp: true}).Extract()
+	n, err := networks.Create(Client, networks.CreateOpts{Name: "tmp_network", AdminStateUp: networks.Up}).Extract()
 	th.AssertNoErr(t, err)
 	networkID := n.ID
 	defer networks.Delete(Client, networkID)
diff --git a/openstack/blockstorage/v1/apiversions/results.go b/openstack/blockstorage/v1/apiversions/results.go
index c4ac157..ea2f7f5 100644
--- a/openstack/blockstorage/v1/apiversions/results.go
+++ b/openstack/blockstorage/v1/apiversions/results.go
@@ -37,11 +37,8 @@
 	}
 
 	err := mapstructure.Decode(page.(APIVersionPage).Body, &resp)
-	if err != nil {
-		return nil, err
-	}
 
-	return resp.Versions, nil
+	return resp.Versions, err
 }
 
 // GetResult represents the result of a get operation.
@@ -56,9 +53,6 @@
 	}
 
 	err := mapstructure.Decode(r.Resp, &resp)
-	if err != nil {
-		return nil, err
-	}
 
-	return resp.Version, nil
+	return resp.Version, err
 }
diff --git a/openstack/blockstorage/v1/snapshots/results.go b/openstack/blockstorage/v1/snapshots/results.go
index 9509bca..d23090d 100644
--- a/openstack/blockstorage/v1/snapshots/results.go
+++ b/openstack/blockstorage/v1/snapshots/results.go
@@ -1,8 +1,6 @@
 package snapshots
 
 import (
-	"fmt"
-
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
 
@@ -91,8 +89,6 @@
 	}
 
 	err := mapstructure.Decode(r.Resp, &res)
-	if err != nil {
-		return nil, fmt.Errorf("snapshots: Error decoding snapshots.commonResult: %v", err)
-	}
-	return res.Snapshot, nil
+
+	return res.Snapshot, err
 }
diff --git a/openstack/blockstorage/v1/volumes/results.go b/openstack/blockstorage/v1/volumes/results.go
index 78c863f..78eb6c1 100644
--- a/openstack/blockstorage/v1/volumes/results.go
+++ b/openstack/blockstorage/v1/volumes/results.go
@@ -1,8 +1,6 @@
 package volumes
 
 import (
-	"fmt"
-
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
 
@@ -80,8 +78,6 @@
 	}
 
 	err := mapstructure.Decode(r.Resp, &res)
-	if err != nil {
-		return nil, fmt.Errorf("volumes: Error decoding volumes.commonResult: %v", err)
-	}
-	return res.Volume, nil
+
+	return res.Volume, err
 }
diff --git a/openstack/blockstorage/v1/volumetypes/results.go b/openstack/blockstorage/v1/volumetypes/results.go
index 8e5932a..77cc1f5 100644
--- a/openstack/blockstorage/v1/volumetypes/results.go
+++ b/openstack/blockstorage/v1/volumetypes/results.go
@@ -1,8 +1,6 @@
 package volumetypes
 
 import (
-	"fmt"
-
 	"github.com/mitchellh/mapstructure"
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
@@ -64,9 +62,6 @@
 	}
 
 	err := mapstructure.Decode(r.Resp, &res)
-	if err != nil {
-		return nil, fmt.Errorf("Error decoding Volume Type: %v", err)
-	}
 
-	return res.VolumeType, nil
+	return res.VolumeType, err
 }
diff --git a/openstack/common/extensions/results.go b/openstack/common/extensions/results.go
index 9319018..e98e5d6 100755
--- a/openstack/common/extensions/results.go
+++ b/openstack/common/extensions/results.go
@@ -1,8 +1,6 @@
 package extensions
 
 import (
-	"fmt"
-
 	"github.com/mitchellh/mapstructure"
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
@@ -25,11 +23,8 @@
 	}
 
 	err := mapstructure.Decode(r.Resp, &res)
-	if err != nil {
-		return nil, fmt.Errorf("Error decoding OpenStack extension: %v", err)
-	}
 
-	return res.Extension, nil
+	return res.Extension, err
 }
 
 // Extension is a struct that represents an OpenStack extension.
@@ -65,9 +60,6 @@
 	}
 
 	err := mapstructure.Decode(page.(ExtensionPage).Body, &resp)
-	if err != nil {
-		return nil, err
-	}
 
-	return resp.Extensions, nil
+	return resp.Extensions, err
 }
diff --git a/openstack/compute/v2/flavors/results.go b/openstack/compute/v2/flavors/results.go
index 68c8f58..1e274e3 100644
--- a/openstack/compute/v2/flavors/results.go
+++ b/openstack/compute/v2/flavors/results.go
@@ -78,12 +78,8 @@
 
 // NextPageURL uses the response's embedded link reference to navigate to the next page of results.
 func (p FlavorPage) NextPageURL() (string, error) {
-	type link struct {
-		Href string `mapstructure:"href"`
-		Rel  string `mapstructure:"rel"`
-	}
 	type resp struct {
-		Links []link `mapstructure:"flavors_links"`
+		Links []gophercloud.Link `mapstructure:"flavors_links"`
 	}
 
 	var r resp
@@ -92,17 +88,7 @@
 		return "", err
 	}
 
-	var url string
-	for _, l := range r.Links {
-		if l.Rel == "next" {
-			url = l.Href
-		}
-	}
-	if url == "" {
-		return "", nil
-	}
-
-	return url, nil
+	return gophercloud.ExtractNextURL(r.Links)
 }
 
 func defaulter(from, to reflect.Kind, v interface{}) (interface{}, error) {
diff --git a/openstack/compute/v2/images/results.go b/openstack/compute/v2/images/results.go
index f93c90c..3c22eeb 100644
--- a/openstack/compute/v2/images/results.go
+++ b/openstack/compute/v2/images/results.go
@@ -65,12 +65,8 @@
 
 // NextPageURL uses the response's embedded link reference to navigate to the next page of results.
 func (page ImagePage) NextPageURL() (string, error) {
-	type link struct {
-		Href string `mapstructure:"href"`
-		Rel  string `mapstructure:"rel"`
-	}
 	type resp struct {
-		Links []link `mapstructure:"images_links"`
+		Links []gophercloud.Link `mapstructure:"images_links"`
 	}
 
 	var r resp
@@ -79,17 +75,7 @@
 		return "", err
 	}
 
-	var url string
-	for _, l := range r.Links {
-		if l.Rel == "next" {
-			url = l.Href
-		}
-	}
-	if url == "" {
-		return "", nil
-	}
-
-	return url, nil
+	return gophercloud.ExtractNextURL(r.Links)
 }
 
 // ExtractImages converts a page of List results into a slice of usable Image structs.
diff --git a/openstack/compute/v2/servers/results.go b/openstack/compute/v2/servers/results.go
index dd2e651..d284ed8 100644
--- a/openstack/compute/v2/servers/results.go
+++ b/openstack/compute/v2/servers/results.go
@@ -116,12 +116,8 @@
 
 // NextPageURL uses the response's embedded link reference to navigate to the next page of results.
 func (page ServerPage) NextPageURL() (string, error) {
-	type link struct {
-		Href string `mapstructure:"href"`
-		Rel  string `mapstructure:"rel"`
-	}
 	type resp struct {
-		Links []link `mapstructure:"servers_links"`
+		Links []gophercloud.Link `mapstructure:"servers_links"`
 	}
 
 	var r resp
@@ -130,17 +126,7 @@
 		return "", err
 	}
 
-	var url string
-	for _, l := range r.Links {
-		if l.Rel == "next" {
-			url = l.Href
-		}
-	}
-	if url == "" {
-		return "", nil
-	}
-
-	return url, nil
+	return gophercloud.ExtractNextURL(r.Links)
 }
 
 // ExtractServers interprets the results of a single page from a List() call, producing a slice of Server entities.
diff --git a/openstack/identity/v2/tenants/results.go b/openstack/identity/v2/tenants/results.go
index e4e3f47..c1220c3 100644
--- a/openstack/identity/v2/tenants/results.go
+++ b/openstack/identity/v2/tenants/results.go
@@ -2,6 +2,7 @@
 
 import (
 	"github.com/mitchellh/mapstructure"
+	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
 )
 
@@ -36,12 +37,8 @@
 
 // NextPageURL extracts the "next" link from the tenants_links section of the result.
 func (page TenantPage) NextPageURL() (string, error) {
-	type link struct {
-		Href string `mapstructure:"href"`
-		Rel  string `mapstructure:"rel"`
-	}
 	type resp struct {
-		Links []link `mapstructure:"tenants_links"`
+		Links []gophercloud.Link `mapstructure:"tenants_links"`
 	}
 
 	var r resp
@@ -50,17 +47,7 @@
 		return "", err
 	}
 
-	var url string
-	for _, l := range r.Links {
-		if l.Rel == "next" {
-			url = l.Href
-		}
-	}
-	if url == "" {
-		return "", nil
-	}
-
-	return url, nil
+	return gophercloud.ExtractNextURL(r.Links)
 }
 
 // ExtractTenants returns a slice of Tenants contained in a single page of results.
diff --git a/openstack/identity/v3/endpoints/results.go b/openstack/identity/v3/endpoints/results.go
index 2dd2357..d1c2472 100644
--- a/openstack/identity/v3/endpoints/results.go
+++ b/openstack/identity/v3/endpoints/results.go
@@ -1,8 +1,6 @@
 package endpoints
 
 import (
-	"fmt"
-
 	"github.com/mitchellh/mapstructure"
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
@@ -24,11 +22,8 @@
 	}
 
 	err := mapstructure.Decode(r.Resp, &res)
-	if err != nil {
-		return nil, fmt.Errorf("Error decoding Endpoint: %v", err)
-	}
 
-	return &res.Endpoint, nil
+	return &res.Endpoint, err
 }
 
 // CreateResult is the deferred result of a Create call.
@@ -77,8 +72,6 @@
 	}
 
 	err := mapstructure.Decode(page.(EndpointPage).Body, &response)
-	if err != nil {
-		return nil, err
-	}
-	return response.Endpoints, nil
+
+	return response.Endpoints, err
 }
diff --git a/openstack/identity/v3/services/results.go b/openstack/identity/v3/services/results.go
index b4e7bd2..e4e068b 100644
--- a/openstack/identity/v3/services/results.go
+++ b/openstack/identity/v3/services/results.go
@@ -1,8 +1,6 @@
 package services
 
 import (
-	"fmt"
-
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
 
@@ -25,11 +23,8 @@
 	}
 
 	err := mapstructure.Decode(r.Resp, &res)
-	if err != nil {
-		return nil, fmt.Errorf("Error decoding Service: %v", err)
-	}
 
-	return &res.Service, nil
+	return &res.Service, err
 }
 
 // CreateResult is the deferred result of a Create call.
diff --git a/openstack/identity/v3/tokens/results.go b/openstack/identity/v3/tokens/results.go
index e96da51..1be98cb 100644
--- a/openstack/identity/v3/tokens/results.go
+++ b/openstack/identity/v3/tokens/results.go
@@ -40,11 +40,8 @@
 
 	// Attempt to parse the timestamp.
 	token.ExpiresAt, err = time.Parse(gophercloud.RFC3339Milli, response.Token.ExpiresAt)
-	if err != nil {
-		return nil, err
-	}
 
-	return &token, nil
+	return &token, err
 }
 
 // CreateResult is the deferred response from a Create call.
diff --git a/openstack/networking/v2/apiversions/requests_test.go b/openstack/networking/v2/apiversions/requests_test.go
index c49b509..d35af9f 100644
--- a/openstack/networking/v2/apiversions/requests_test.go
+++ b/openstack/networking/v2/apiversions/requests_test.go
@@ -65,6 +65,22 @@
 	}
 }
 
+func TestNonJSONCannotBeExtractedIntoAPIVersions(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		w.WriteHeader(http.StatusOK)
+	})
+
+	ListVersions(fake.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) {
+		if _, err := ExtractAPIVersions(page); err == nil {
+			t.Fatalf("Expected error, got nil")
+		}
+		return true, nil
+	})
+}
+
 func TestAPIInfo(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
@@ -148,3 +164,19 @@
 		t.Errorf("Expected 1 page, got %d", count)
 	}
 }
+
+func TestNonJSONCannotBeExtractedIntoAPIVersionResources(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		w.WriteHeader(http.StatusOK)
+	})
+
+	ListVersionResources(fake.ServiceClient(), "v2.0").EachPage(func(page pagination.Page) (bool, error) {
+		if _, err := ExtractVersionResources(page); err == nil {
+			t.Fatalf("Expected error, got nil")
+		}
+		return true, nil
+	})
+}
diff --git a/openstack/networking/v2/apiversions/results.go b/openstack/networking/v2/apiversions/results.go
index e13998d..9715934 100644
--- a/openstack/networking/v2/apiversions/results.go
+++ b/openstack/networking/v2/apiversions/results.go
@@ -35,11 +35,8 @@
 	}
 
 	err := mapstructure.Decode(page.(APIVersionPage).Body, &resp)
-	if err != nil {
-		return nil, err
-	}
 
-	return resp.Versions, nil
+	return resp.Versions, err
 }
 
 // APIVersionResource represents a generic API resource. It contains the name
@@ -75,9 +72,6 @@
 	}
 
 	err := mapstructure.Decode(page.(APIVersionResourcePage).Body, &resp)
-	if err != nil {
-		return nil, err
-	}
 
-	return resp.APIVersionResources, nil
+	return resp.APIVersionResources, err
 }
diff --git a/openstack/networking/v2/apiversions/urls.go b/openstack/networking/v2/apiversions/urls.go
index c43f991..58aa2b6 100644
--- a/openstack/networking/v2/apiversions/urls.go
+++ b/openstack/networking/v2/apiversions/urls.go
@@ -7,9 +7,9 @@
 )
 
 func apiVersionsURL(c *gophercloud.ServiceClient) string {
-	return c.ServiceURL("")
+	return c.Endpoint
 }
 
 func apiInfoURL(c *gophercloud.ServiceClient, version string) string {
-	return c.ServiceURL(strings.TrimRight(version, "/") + "/")
+	return c.Endpoint + strings.TrimRight(version, "/") + "/"
 }
diff --git a/openstack/networking/v2/common/common_tests.go b/openstack/networking/v2/common/common_tests.go
new file mode 100644
index 0000000..4160351
--- /dev/null
+++ b/openstack/networking/v2/common/common_tests.go
@@ -0,0 +1,14 @@
+package common
+
+import (
+	"github.com/rackspace/gophercloud"
+	"github.com/rackspace/gophercloud/testhelper/client"
+)
+
+const TokenID = client.TokenID
+
+func ServiceClient() *gophercloud.ServiceClient {
+	sc := client.ServiceClient()
+	sc.ResourceBase = sc.Endpoint + "v2.0/"
+	return sc
+}
diff --git a/openstack/networking/v2/extensions/delegate_test.go b/openstack/networking/v2/extensions/delegate_test.go
index 8de8906..3d2ac78 100755
--- a/openstack/networking/v2/extensions/delegate_test.go
+++ b/openstack/networking/v2/extensions/delegate_test.go
@@ -6,16 +6,16 @@
 	"testing"
 
 	common "github.com/rackspace/gophercloud/openstack/common/extensions"
+	fake "github.com/rackspace/gophercloud/openstack/networking/v2/common"
 	"github.com/rackspace/gophercloud/pagination"
 	th "github.com/rackspace/gophercloud/testhelper"
-	fake "github.com/rackspace/gophercloud/testhelper/client"
 )
 
 func TestList(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 
-	th.Mux.HandleFunc("/extensions", func(w http.ResponseWriter, r *http.Request) {
+	th.Mux.HandleFunc("/v2.0/extensions", func(w http.ResponseWriter, r *http.Request) {
 		th.TestMethod(t, r, "GET")
 		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
 
@@ -73,7 +73,7 @@
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 
-	th.Mux.HandleFunc("/extensions/agent", func(w http.ResponseWriter, r *http.Request) {
+	th.Mux.HandleFunc("/v2.0/extensions/agent", func(w http.ResponseWriter, r *http.Request) {
 		th.TestMethod(t, r, "GET")
 		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
 
@@ -92,14 +92,14 @@
     }
 }
     `)
-
-		ext, err := Get(fake.ServiceClient(), "agent").Extract()
-		th.AssertNoErr(t, err)
-
-		th.AssertEquals(t, ext.Updated, "2013-02-03T10:00:00-00:00")
-		th.AssertEquals(t, ext.Name, "agent")
-		th.AssertEquals(t, ext.Namespace, "http://docs.openstack.org/ext/agent/api/v2.0")
-		th.AssertEquals(t, ext.Alias, "agent")
-		th.AssertEquals(t, ext.Description, "The agent management extension.")
 	})
+
+	ext, err := Get(fake.ServiceClient(), "agent").Extract()
+	th.AssertNoErr(t, err)
+
+	th.AssertEquals(t, ext.Updated, "2013-02-03T10:00:00-00:00")
+	th.AssertEquals(t, ext.Name, "agent")
+	th.AssertEquals(t, ext.Namespace, "http://docs.openstack.org/ext/agent/api/v2.0")
+	th.AssertEquals(t, ext.Alias, "agent")
+	th.AssertEquals(t, ext.Description, "The agent management extension.")
 }
diff --git a/openstack/networking/v2/extensions/external/results.go b/openstack/networking/v2/extensions/external/results.go
index 4cd2133..47f7258 100644
--- a/openstack/networking/v2/extensions/external/results.go
+++ b/openstack/networking/v2/extensions/external/results.go
@@ -1,8 +1,6 @@
 package external
 
 import (
-	"fmt"
-
 	"github.com/mitchellh/mapstructure"
 	"github.com/rackspace/gophercloud/openstack/networking/v2/networks"
 	"github.com/rackspace/gophercloud/pagination"
@@ -37,52 +35,36 @@
 	External bool `mapstructure:"router:external" json:"router:external"`
 }
 
-// ExtractGet decorates a GetResult struct returned from a networks.Get()
-// function with extended attributes.
-func ExtractGet(r networks.GetResult) (*NetworkExternal, error) {
-	if r.Err != nil {
-		return nil, r.Err
+func commonExtract(e error, response map[string]interface{}) (*NetworkExternal, error) {
+	if e != nil {
+		return nil, e
 	}
+
 	var res struct {
 		Network *NetworkExternal `json:"network"`
 	}
-	err := mapstructure.Decode(r.Resp, &res)
-	if err != nil {
-		return nil, fmt.Errorf("Error decoding Neutron network: %v", err)
-	}
-	return res.Network, nil
+
+	err := mapstructure.Decode(response, &res)
+
+	return res.Network, err
+}
+
+// ExtractGet decorates a GetResult struct returned from a networks.Get()
+// function with extended attributes.
+func ExtractGet(r networks.GetResult) (*NetworkExternal, error) {
+	return commonExtract(r.Err, r.Resp)
 }
 
 // ExtractCreate decorates a CreateResult struct returned from a networks.Create()
 // function with extended attributes.
 func ExtractCreate(r networks.CreateResult) (*NetworkExternal, error) {
-	if r.Err != nil {
-		return nil, r.Err
-	}
-	var res struct {
-		Network *NetworkExternal `json:"network"`
-	}
-	err := mapstructure.Decode(r.Resp, &res)
-	if err != nil {
-		return nil, fmt.Errorf("Error decoding Neutron network: %v", err)
-	}
-	return res.Network, nil
+	return commonExtract(r.Err, r.Resp)
 }
 
 // ExtractUpdate decorates a UpdateResult struct returned from a
 // networks.Update() function with extended attributes.
 func ExtractUpdate(r networks.UpdateResult) (*NetworkExternal, error) {
-	if r.Err != nil {
-		return nil, r.Err
-	}
-	var res struct {
-		Network *NetworkExternal `json:"network"`
-	}
-	err := mapstructure.Decode(r.Resp, &res)
-	if err != nil {
-		return nil, fmt.Errorf("Error decoding Neutron network: %v", err)
-	}
-	return res.Network, nil
+	return commonExtract(r.Err, r.Resp)
 }
 
 // ExtractList accepts a Page struct, specifically a NetworkPage struct, and
@@ -94,9 +76,6 @@
 	}
 
 	err := mapstructure.Decode(page.(networks.NetworkPage).Body, &resp)
-	if err != nil {
-		return nil, err
-	}
 
-	return resp.Networks, nil
+	return resp.Networks, err
 }
diff --git a/openstack/networking/v2/extensions/external/results_test.go b/openstack/networking/v2/extensions/external/results_test.go
index 6bed126..916cd2c 100644
--- a/openstack/networking/v2/extensions/external/results_test.go
+++ b/openstack/networking/v2/extensions/external/results_test.go
@@ -1,6 +1,7 @@
 package external
 
 import (
+	"errors"
 	"fmt"
 	"net/http"
 	"testing"
@@ -228,3 +229,26 @@
 	th.AssertNoErr(t, err)
 	th.AssertEquals(t, true, n.External)
 }
+
+func TestExtractFnsReturnsErrWhenResultContainsErr(t *testing.T) {
+	gr := networks.GetResult{}
+	gr.Err = errors.New("")
+
+	if _, err := ExtractGet(gr); err == nil {
+		t.Fatalf("Expected error, got one")
+	}
+
+	ur := networks.UpdateResult{}
+	ur.Err = errors.New("")
+
+	if _, err := ExtractUpdate(ur); err == nil {
+		t.Fatalf("Expected error, got one")
+	}
+
+	cr := networks.CreateResult{}
+	cr.Err = errors.New("")
+
+	if _, err := ExtractCreate(cr); err == nil {
+		t.Fatalf("Expected error, got one")
+	}
+}
diff --git a/openstack/networking/v2/extensions/layer3/floatingips/requests_test.go b/openstack/networking/v2/extensions/layer3/floatingips/requests_test.go
index b9153fc..19614be 100644
--- a/openstack/networking/v2/extensions/layer3/floatingips/requests_test.go
+++ b/openstack/networking/v2/extensions/layer3/floatingips/requests_test.go
@@ -5,9 +5,9 @@
 	"net/http"
 	"testing"
 
+	fake "github.com/rackspace/gophercloud/openstack/networking/v2/common"
 	"github.com/rackspace/gophercloud/pagination"
 	th "github.com/rackspace/gophercloud/testhelper"
-	fake "github.com/rackspace/gophercloud/testhelper/client"
 )
 
 func TestList(t *testing.T) {
@@ -90,6 +90,34 @@
 	}
 }
 
+func TestInvalidNextPageURLs(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	th.Mux.HandleFunc("/v2.0/floatingips", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintf(w, `{"floatingips": [{}], "floatingips_links": {}}`)
+	})
+
+	List(fake.ServiceClient(), ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
+		ExtractFloatingIPs(page)
+		return true, nil
+	})
+}
+
+func TestRequiredFieldsForCreate(t *testing.T) {
+	res1 := Create(fake.ServiceClient(), CreateOpts{FloatingNetworkID: ""})
+	if res1.Err == nil {
+		t.Fatalf("Expected error, got none")
+	}
+
+	res2 := Create(fake.ServiceClient(), CreateOpts{FloatingNetworkID: "foo", PortID: ""})
+	if res2.Err == nil {
+		t.Fatalf("Expected error, got none")
+	}
+}
+
 func TestCreate(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
diff --git a/openstack/networking/v2/extensions/layer3/floatingips/results.go b/openstack/networking/v2/extensions/layer3/floatingips/results.go
index 4857c92..43892f0 100644
--- a/openstack/networking/v2/extensions/layer3/floatingips/results.go
+++ b/openstack/networking/v2/extensions/layer3/floatingips/results.go
@@ -89,12 +89,8 @@
 // the end of a page and the pager seeks to traverse over a new one. In order
 // to do this, it needs to construct the next page's URL.
 func (p FloatingIPPage) NextPageURL() (string, error) {
-	type link struct {
-		Href string `mapstructure:"href"`
-		Rel  string `mapstructure:"rel"`
-	}
 	type resp struct {
-		Links []link `mapstructure:"floatingips_links"`
+		Links []gophercloud.Link `mapstructure:"floatingips_links"`
 	}
 
 	var r resp
@@ -103,17 +99,7 @@
 		return "", err
 	}
 
-	var url string
-	for _, l := range r.Links {
-		if l.Rel == "next" {
-			url = l.Href
-		}
-	}
-	if url == "" {
-		return "", nil
-	}
-
-	return url, nil
+	return gophercloud.ExtractNextURL(r.Links)
 }
 
 // IsEmpty checks whether a NetworkPage struct is empty.
@@ -134,9 +120,6 @@
 	}
 
 	err := mapstructure.Decode(page.(FloatingIPPage).Body, &resp)
-	if err != nil {
-		return nil, err
-	}
 
-	return resp.FloatingIPs, nil
+	return resp.FloatingIPs, err
 }
diff --git a/openstack/networking/v2/extensions/layer3/floatingips/urls.go b/openstack/networking/v2/extensions/layer3/floatingips/urls.go
index dbe3f9f..355f20d 100644
--- a/openstack/networking/v2/extensions/layer3/floatingips/urls.go
+++ b/openstack/networking/v2/extensions/layer3/floatingips/urls.go
@@ -2,15 +2,12 @@
 
 import "github.com/rackspace/gophercloud"
 
-const (
-	version      = "v2.0"
-	resourcePath = "floatingips"
-)
+const resourcePath = "floatingips"
 
 func rootURL(c *gophercloud.ServiceClient) string {
-	return c.ServiceURL(version, resourcePath)
+	return c.ServiceURL(resourcePath)
 }
 
 func resourceURL(c *gophercloud.ServiceClient, id string) string {
-	return c.ServiceURL(version, resourcePath, id)
+	return c.ServiceURL(resourcePath, id)
 }
diff --git a/openstack/networking/v2/extensions/layer3/routers/requests_test.go b/openstack/networking/v2/extensions/layer3/routers/requests_test.go
index 56a0d74..c34264d 100755
--- a/openstack/networking/v2/extensions/layer3/routers/requests_test.go
+++ b/openstack/networking/v2/extensions/layer3/routers/requests_test.go
@@ -5,9 +5,9 @@
 	"net/http"
 	"testing"
 
+	fake "github.com/rackspace/gophercloud/openstack/networking/v2/common"
 	"github.com/rackspace/gophercloud/pagination"
 	th "github.com/rackspace/gophercloud/testhelper"
-	fake "github.com/rackspace/gophercloud/testhelper/client"
 )
 
 func TestURLs(t *testing.T) {
@@ -288,6 +288,17 @@
 	th.AssertEquals(t, "9a83fa11-8da5-436e-9afe-3d3ac5ce7770", res.ID)
 }
 
+func TestAddInterfaceRequiredOpts(t *testing.T) {
+	_, err := AddInterface(fake.ServiceClient(), "foo", InterfaceOpts{}).Extract()
+	if err == nil {
+		t.Fatalf("Expected error, got none")
+	}
+	_, err = AddInterface(fake.ServiceClient(), "foo", InterfaceOpts{SubnetID: "bar", PortID: "baz"}).Extract()
+	if err == nil {
+		t.Fatalf("Expected error, got none")
+	}
+}
+
 func TestRemoveInterface(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
diff --git a/openstack/networking/v2/extensions/layer3/routers/results.go b/openstack/networking/v2/extensions/layer3/routers/results.go
index d14578c..eae647f 100755
--- a/openstack/networking/v2/extensions/layer3/routers/results.go
+++ b/openstack/networking/v2/extensions/layer3/routers/results.go
@@ -1,8 +1,6 @@
 package routers
 
 import (
-	"fmt"
-
 	"github.com/mitchellh/mapstructure"
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
@@ -53,12 +51,8 @@
 // the end of a page and the pager seeks to traverse over a new one. In order
 // to do this, it needs to construct the next page's URL.
 func (p RouterPage) NextPageURL() (string, error) {
-	type link struct {
-		Href string `mapstructure:"href"`
-		Rel  string `mapstructure:"rel"`
-	}
 	type resp struct {
-		Links []link `mapstructure:"routers_links"`
+		Links []gophercloud.Link `mapstructure:"routers_links"`
 	}
 
 	var r resp
@@ -67,17 +61,7 @@
 		return "", err
 	}
 
-	var url string
-	for _, l := range r.Links {
-		if l.Rel == "next" {
-			url = l.Href
-		}
-	}
-	if url == "" {
-		return "", nil
-	}
-
-	return url, nil
+	return gophercloud.ExtractNextURL(r.Links)
 }
 
 // IsEmpty checks whether a RouterPage struct is empty.
@@ -98,11 +82,8 @@
 	}
 
 	err := mapstructure.Decode(page.(RouterPage).Body, &resp)
-	if err != nil {
-		return nil, err
-	}
 
-	return resp.Routers, nil
+	return resp.Routers, err
 }
 
 type commonResult struct {
@@ -120,11 +101,8 @@
 	}
 
 	err := mapstructure.Decode(r.Resp, &res)
-	if err != nil {
-		return nil, fmt.Errorf("Error decoding Neutron router: %v", err)
-	}
 
-	return res.Router, nil
+	return res.Router, err
 }
 
 // CreateResult represents the result of a create operation.
@@ -176,9 +154,6 @@
 
 	var res *InterfaceInfo
 	err := mapstructure.Decode(r.Resp, &res)
-	if err != nil {
-		return nil, fmt.Errorf("Error decoding Neutron router interface: %v", err)
-	}
 
-	return res, nil
+	return res, err
 }
diff --git a/openstack/networking/v2/extensions/layer3/routers/urls.go b/openstack/networking/v2/extensions/layer3/routers/urls.go
index 512c8a4..bc22c2a 100644
--- a/openstack/networking/v2/extensions/layer3/routers/urls.go
+++ b/openstack/networking/v2/extensions/layer3/routers/urls.go
@@ -2,23 +2,20 @@
 
 import "github.com/rackspace/gophercloud"
 
-const (
-	version      = "v2.0"
-	resourcePath = "routers"
-)
+const resourcePath = "routers"
 
 func rootURL(c *gophercloud.ServiceClient) string {
-	return c.ServiceURL(version, resourcePath)
+	return c.ServiceURL(resourcePath)
 }
 
 func resourceURL(c *gophercloud.ServiceClient, id string) string {
-	return c.ServiceURL(version, resourcePath, id)
+	return c.ServiceURL(resourcePath, id)
 }
 
 func addInterfaceURL(c *gophercloud.ServiceClient, id string) string {
-	return c.ServiceURL(version, resourcePath, id, "add_router_interface")
+	return c.ServiceURL(resourcePath, id, "add_router_interface")
 }
 
 func removeInterfaceURL(c *gophercloud.ServiceClient, id string) string {
-	return c.ServiceURL(version, resourcePath, id, "remove_router_interface")
+	return c.ServiceURL(resourcePath, id, "remove_router_interface")
 }
diff --git a/openstack/networking/v2/extensions/lbaas/members/requests_test.go b/openstack/networking/v2/extensions/lbaas/members/requests_test.go
index cae3524..dc1ece3 100644
--- a/openstack/networking/v2/extensions/lbaas/members/requests_test.go
+++ b/openstack/networking/v2/extensions/lbaas/members/requests_test.go
@@ -5,9 +5,9 @@
 	"net/http"
 	"testing"
 
+	fake "github.com/rackspace/gophercloud/openstack/networking/v2/common"
 	"github.com/rackspace/gophercloud/pagination"
 	th "github.com/rackspace/gophercloud/testhelper"
-	fake "github.com/rackspace/gophercloud/testhelper/client"
 )
 
 func TestURLs(t *testing.T) {
diff --git a/openstack/networking/v2/extensions/lbaas/members/results.go b/openstack/networking/v2/extensions/lbaas/members/results.go
index 9466fee..b006551 100644
--- a/openstack/networking/v2/extensions/lbaas/members/results.go
+++ b/openstack/networking/v2/extensions/lbaas/members/results.go
@@ -1,8 +1,6 @@
 package members
 
 import (
-	"fmt"
-
 	"github.com/mitchellh/mapstructure"
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
@@ -46,12 +44,8 @@
 // the end of a page and the pager seeks to traverse over a new one. In order
 // to do this, it needs to construct the next page's URL.
 func (p MemberPage) NextPageURL() (string, error) {
-	type link struct {
-		Href string `mapstructure:"href"`
-		Rel  string `mapstructure:"rel"`
-	}
 	type resp struct {
-		Links []link `mapstructure:"members_links"`
+		Links []gophercloud.Link `mapstructure:"members_links"`
 	}
 
 	var r resp
@@ -60,17 +54,7 @@
 		return "", err
 	}
 
-	var url string
-	for _, l := range r.Links {
-		if l.Rel == "next" {
-			url = l.Href
-		}
-	}
-	if url == "" {
-		return "", nil
-	}
-
-	return url, nil
+	return gophercloud.ExtractNextURL(r.Links)
 }
 
 // IsEmpty checks whether a MemberPage struct is empty.
@@ -113,11 +97,8 @@
 	}
 
 	err := mapstructure.Decode(r.Resp, &res)
-	if err != nil {
-		return nil, fmt.Errorf("Error decoding Neutron member: %v", err)
-	}
 
-	return res.Member, nil
+	return res.Member, err
 }
 
 // CreateResult represents the result of a create operation.
diff --git a/openstack/networking/v2/extensions/lbaas/members/urls.go b/openstack/networking/v2/extensions/lbaas/members/urls.go
index 9d5ecec..94b57e4 100644
--- a/openstack/networking/v2/extensions/lbaas/members/urls.go
+++ b/openstack/networking/v2/extensions/lbaas/members/urls.go
@@ -3,15 +3,14 @@
 import "github.com/rackspace/gophercloud"
 
 const (
-	version      = "v2.0"
 	rootPath     = "lb"
 	resourcePath = "members"
 )
 
 func rootURL(c *gophercloud.ServiceClient) string {
-	return c.ServiceURL(version, rootPath, resourcePath)
+	return c.ServiceURL(rootPath, resourcePath)
 }
 
 func resourceURL(c *gophercloud.ServiceClient, id string) string {
-	return c.ServiceURL(version, rootPath, resourcePath, id)
+	return c.ServiceURL(rootPath, resourcePath, id)
 }
diff --git a/openstack/networking/v2/extensions/lbaas/monitors/requests_test.go b/openstack/networking/v2/extensions/lbaas/monitors/requests_test.go
index 68a3288..5c5a1d2 100644
--- a/openstack/networking/v2/extensions/lbaas/monitors/requests_test.go
+++ b/openstack/networking/v2/extensions/lbaas/monitors/requests_test.go
@@ -5,9 +5,9 @@
 	"net/http"
 	"testing"
 
+	fake "github.com/rackspace/gophercloud/openstack/networking/v2/common"
 	"github.com/rackspace/gophercloud/pagination"
 	th "github.com/rackspace/gophercloud/testhelper"
-	fake "github.com/rackspace/gophercloud/testhelper/client"
 )
 
 func TestURLs(t *testing.T) {
@@ -158,6 +158,17 @@
 	th.AssertNoErr(t, err)
 }
 
+func TestRequiredCreateOpts(t *testing.T) {
+	res := Create(fake.ServiceClient(), CreateOpts{})
+	if res.Err == nil {
+		t.Fatalf("Expected error, got none")
+	}
+	res = Create(fake.ServiceClient(), CreateOpts{Type: TypeHTTP})
+	if res.Err == nil {
+		t.Fatalf("Expected error, got none")
+	}
+}
+
 func TestGet(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
diff --git a/openstack/networking/v2/extensions/lbaas/monitors/results.go b/openstack/networking/v2/extensions/lbaas/monitors/results.go
index a41f20e..656ab1d 100644
--- a/openstack/networking/v2/extensions/lbaas/monitors/results.go
+++ b/openstack/networking/v2/extensions/lbaas/monitors/results.go
@@ -1,8 +1,6 @@
 package monitors
 
 import (
-	"fmt"
-
 	"github.com/mitchellh/mapstructure"
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
@@ -74,12 +72,8 @@
 // the end of a page and the pager seeks to traverse over a new one. In order
 // to do this, it needs to construct the next page's URL.
 func (p MonitorPage) NextPageURL() (string, error) {
-	type link struct {
-		Href string `mapstructure:"href"`
-		Rel  string `mapstructure:"rel"`
-	}
 	type resp struct {
-		Links []link `mapstructure:"health_monitors_links"`
+		Links []gophercloud.Link `mapstructure:"health_monitors_links"`
 	}
 
 	var r resp
@@ -88,17 +82,7 @@
 		return "", err
 	}
 
-	var url string
-	for _, l := range r.Links {
-		if l.Rel == "next" {
-			url = l.Href
-		}
-	}
-	if url == "" {
-		return "", nil
-	}
-
-	return url, nil
+	return gophercloud.ExtractNextURL(r.Links)
 }
 
 // IsEmpty checks whether a PoolPage struct is empty.
@@ -119,11 +103,8 @@
 	}
 
 	err := mapstructure.Decode(page.(MonitorPage).Body, &resp)
-	if err != nil {
-		return nil, err
-	}
 
-	return resp.Monitors, nil
+	return resp.Monitors, err
 }
 
 type commonResult struct {
@@ -141,11 +122,8 @@
 	}
 
 	err := mapstructure.Decode(r.Resp, &res)
-	if err != nil {
-		return nil, fmt.Errorf("Error decoding Neutron monitor: %v", err)
-	}
 
-	return res.Monitor, nil
+	return res.Monitor, err
 }
 
 // CreateResult represents the result of a create operation.
diff --git a/openstack/networking/v2/extensions/lbaas/monitors/urls.go b/openstack/networking/v2/extensions/lbaas/monitors/urls.go
index e4b2afc..46e84bb 100644
--- a/openstack/networking/v2/extensions/lbaas/monitors/urls.go
+++ b/openstack/networking/v2/extensions/lbaas/monitors/urls.go
@@ -3,15 +3,14 @@
 import "github.com/rackspace/gophercloud"
 
 const (
-	version      = "v2.0"
 	rootPath     = "lb"
 	resourcePath = "health_monitors"
 )
 
 func rootURL(c *gophercloud.ServiceClient) string {
-	return c.ServiceURL(version, rootPath, resourcePath)
+	return c.ServiceURL(rootPath, resourcePath)
 }
 
 func resourceURL(c *gophercloud.ServiceClient, id string) string {
-	return c.ServiceURL(version, rootPath, resourcePath, id)
+	return c.ServiceURL(rootPath, resourcePath, id)
 }
diff --git a/openstack/networking/v2/extensions/lbaas/pools/requests_test.go b/openstack/networking/v2/extensions/lbaas/pools/requests_test.go
index 6af47a1..6da29a6 100644
--- a/openstack/networking/v2/extensions/lbaas/pools/requests_test.go
+++ b/openstack/networking/v2/extensions/lbaas/pools/requests_test.go
@@ -5,9 +5,9 @@
 	"net/http"
 	"testing"
 
+	fake "github.com/rackspace/gophercloud/openstack/networking/v2/common"
 	"github.com/rackspace/gophercloud/pagination"
 	th "github.com/rackspace/gophercloud/testhelper"
-	fake "github.com/rackspace/gophercloud/testhelper/client"
 )
 
 func TestURLs(t *testing.T) {
diff --git a/openstack/networking/v2/extensions/lbaas/pools/results.go b/openstack/networking/v2/extensions/lbaas/pools/results.go
index 5b2adde..4233176 100644
--- a/openstack/networking/v2/extensions/lbaas/pools/results.go
+++ b/openstack/networking/v2/extensions/lbaas/pools/results.go
@@ -1,8 +1,6 @@
 package pools
 
 import (
-	"fmt"
-
 	"github.com/mitchellh/mapstructure"
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
@@ -68,12 +66,8 @@
 // the end of a page and the pager seeks to traverse over a new one. In order
 // to do this, it needs to construct the next page's URL.
 func (p PoolPage) NextPageURL() (string, error) {
-	type link struct {
-		Href string `mapstructure:"href"`
-		Rel  string `mapstructure:"rel"`
-	}
 	type resp struct {
-		Links []link `mapstructure:"pools_links"`
+		Links []gophercloud.Link `mapstructure:"pools_links"`
 	}
 
 	var r resp
@@ -82,17 +76,7 @@
 		return "", err
 	}
 
-	var url string
-	for _, l := range r.Links {
-		if l.Rel == "next" {
-			url = l.Href
-		}
-	}
-	if url == "" {
-		return "", nil
-	}
-
-	return url, nil
+	return gophercloud.ExtractNextURL(r.Links)
 }
 
 // IsEmpty checks whether a PoolPage struct is empty.
@@ -113,11 +97,8 @@
 	}
 
 	err := mapstructure.Decode(page.(PoolPage).Body, &resp)
-	if err != nil {
-		return nil, err
-	}
 
-	return resp.Pools, nil
+	return resp.Pools, err
 }
 
 type commonResult struct {
@@ -135,11 +116,8 @@
 	}
 
 	err := mapstructure.Decode(r.Resp, &res)
-	if err != nil {
-		return nil, fmt.Errorf("Error decoding Neutron pool: %v", err)
-	}
 
-	return res.Pool, nil
+	return res.Pool, err
 }
 
 // CreateResult represents the result of a create operation.
diff --git a/openstack/networking/v2/extensions/lbaas/pools/urls.go b/openstack/networking/v2/extensions/lbaas/pools/urls.go
index 124ef5d..6cd15b0 100644
--- a/openstack/networking/v2/extensions/lbaas/pools/urls.go
+++ b/openstack/networking/v2/extensions/lbaas/pools/urls.go
@@ -3,24 +3,23 @@
 import "github.com/rackspace/gophercloud"
 
 const (
-	version      = "v2.0"
 	rootPath     = "lb"
 	resourcePath = "pools"
 	monitorPath  = "health_monitors"
 )
 
 func rootURL(c *gophercloud.ServiceClient) string {
-	return c.ServiceURL(version, rootPath, resourcePath)
+	return c.ServiceURL(rootPath, resourcePath)
 }
 
 func resourceURL(c *gophercloud.ServiceClient, id string) string {
-	return c.ServiceURL(version, rootPath, resourcePath, id)
+	return c.ServiceURL(rootPath, resourcePath, id)
 }
 
 func associateURL(c *gophercloud.ServiceClient, id string) string {
-	return c.ServiceURL(version, rootPath, resourcePath, id, monitorPath)
+	return c.ServiceURL(rootPath, resourcePath, id, monitorPath)
 }
 
 func disassociateURL(c *gophercloud.ServiceClient, poolID, monitorID string) string {
-	return c.ServiceURL(version, rootPath, resourcePath, poolID, monitorPath, monitorID)
+	return c.ServiceURL(rootPath, resourcePath, poolID, monitorPath, monitorID)
 }
diff --git a/openstack/networking/v2/extensions/lbaas/vips/requests_test.go b/openstack/networking/v2/extensions/lbaas/vips/requests_test.go
index f4e90c7..430f1a1 100644
--- a/openstack/networking/v2/extensions/lbaas/vips/requests_test.go
+++ b/openstack/networking/v2/extensions/lbaas/vips/requests_test.go
@@ -5,9 +5,9 @@
 	"net/http"
 	"testing"
 
+	fake "github.com/rackspace/gophercloud/openstack/networking/v2/common"
 	"github.com/rackspace/gophercloud/pagination"
 	th "github.com/rackspace/gophercloud/testhelper"
-	fake "github.com/rackspace/gophercloud/testhelper/client"
 )
 
 func TestURLs(t *testing.T) {
@@ -139,7 +139,8 @@
         "admin_state_up": true,
         "subnet_id": "8032909d-47a1-4715-90af-5153ffe39861",
         "pool_id": "61b1f87a-7a21-4ad3-9dda-7f81d249944f",
-        "protocol_port": 80
+        "protocol_port": 80,
+				"session_persistence": {"type": "SOURCE_IP"}
     }
 }
 			`)
@@ -175,6 +176,7 @@
 		SubnetID:     "8032909d-47a1-4715-90af-5153ffe39861",
 		PoolID:       "61b1f87a-7a21-4ad3-9dda-7f81d249944f",
 		ProtocolPort: 80,
+		Persistence:  &SessionPersistence{Type: "SOURCE_IP"},
 	}
 
 	r, err := Create(fake.ServiceClient(), opts).Extract()
@@ -195,6 +197,29 @@
 	th.AssertEquals(t, "NewVip", r.Name)
 }
 
+func TestRequiredCreateOpts(t *testing.T) {
+	res := Create(fake.ServiceClient(), CreateOpts{})
+	if res.Err == nil {
+		t.Fatalf("Expected error, got none")
+	}
+	res = Create(fake.ServiceClient(), CreateOpts{Name: "foo"})
+	if res.Err == nil {
+		t.Fatalf("Expected error, got none")
+	}
+	res = Create(fake.ServiceClient(), CreateOpts{Name: "foo", SubnetID: "bar"})
+	if res.Err == nil {
+		t.Fatalf("Expected error, got none")
+	}
+	res = Create(fake.ServiceClient(), CreateOpts{Name: "foo", SubnetID: "bar", Protocol: "bar"})
+	if res.Err == nil {
+		t.Fatalf("Expected error, got none")
+	}
+	res = Create(fake.ServiceClient(), CreateOpts{Name: "foo", SubnetID: "bar", Protocol: "bar", ProtocolPort: 80})
+	if res.Err == nil {
+		t.Fatalf("Expected error, got none")
+	}
+}
+
 func TestGet(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
@@ -254,7 +279,8 @@
 		th.TestJSONRequest(t, r, `
 {
     "vip": {
-        "connection_limit": 1000
+        "connection_limit": 1000,
+				"session_persistence": {"type": "SOURCE_IP"}
     }
 }
 			`)
@@ -284,7 +310,10 @@
 	})
 
 	i1000 := 1000
-	options := UpdateOpts{ConnLimit: &i1000}
+	options := UpdateOpts{
+		ConnLimit:   &i1000,
+		Persistence: &SessionPersistence{Type: "SOURCE_IP"},
+	}
 	vip, err := Update(fake.ServiceClient(), "4ec89087-d057-4e2c-911f-60a3b47ee304", options).Extract()
 	th.AssertNoErr(t, err)
 
diff --git a/openstack/networking/v2/extensions/lbaas/vips/results.go b/openstack/networking/v2/extensions/lbaas/vips/results.go
index 62efe09..5925adc 100644
--- a/openstack/networking/v2/extensions/lbaas/vips/results.go
+++ b/openstack/networking/v2/extensions/lbaas/vips/results.go
@@ -1,8 +1,6 @@
 package vips
 
 import (
-	"fmt"
-
 	"github.com/mitchellh/mapstructure"
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
@@ -28,7 +26,7 @@
 	Type string `mapstructure:"type" json:"type"`
 
 	// Name of cookie if persistence mode is set appropriately
-	CookieName string `mapstructure:"cookie_name" json:"cookie_name"`
+	CookieName string `mapstructure:"cookie_name" json:"cookie_name,omitempty"`
 }
 
 // VirtualIP is the primary load balancing configuration object that specifies
@@ -93,12 +91,8 @@
 // the end of a page and the pager seeks to traverse over a new one. In order
 // to do this, it needs to construct the next page's URL.
 func (p VIPPage) NextPageURL() (string, error) {
-	type link struct {
-		Href string `mapstructure:"href"`
-		Rel  string `mapstructure:"rel"`
-	}
 	type resp struct {
-		Links []link `mapstructure:"vips_links"`
+		Links []gophercloud.Link `mapstructure:"vips_links"`
 	}
 
 	var r resp
@@ -107,17 +101,7 @@
 		return "", err
 	}
 
-	var url string
-	for _, l := range r.Links {
-		if l.Rel == "next" {
-			url = l.Href
-		}
-	}
-	if url == "" {
-		return "", nil
-	}
-
-	return url, nil
+	return gophercloud.ExtractNextURL(r.Links)
 }
 
 // IsEmpty checks whether a RouterPage struct is empty.
@@ -138,11 +122,8 @@
 	}
 
 	err := mapstructure.Decode(page.(VIPPage).Body, &resp)
-	if err != nil {
-		return nil, err
-	}
 
-	return resp.VIPs, nil
+	return resp.VIPs, err
 }
 
 type commonResult struct {
@@ -160,11 +141,8 @@
 	}
 
 	err := mapstructure.Decode(r.Resp, &res)
-	if err != nil {
-		return nil, fmt.Errorf("Error decoding Neutron Virtual IP: %v", err)
-	}
 
-	return res.VirtualIP, nil
+	return res.VirtualIP, err
 }
 
 // CreateResult represents the result of a create operation.
diff --git a/openstack/networking/v2/extensions/lbaas/vips/urls.go b/openstack/networking/v2/extensions/lbaas/vips/urls.go
index 570db6d..2b6f67e 100644
--- a/openstack/networking/v2/extensions/lbaas/vips/urls.go
+++ b/openstack/networking/v2/extensions/lbaas/vips/urls.go
@@ -3,15 +3,14 @@
 import "github.com/rackspace/gophercloud"
 
 const (
-	version      = "v2.0"
 	rootPath     = "lb"
 	resourcePath = "vips"
 )
 
 func rootURL(c *gophercloud.ServiceClient) string {
-	return c.ServiceURL(version, rootPath, resourcePath)
+	return c.ServiceURL(rootPath, resourcePath)
 }
 
 func resourceURL(c *gophercloud.ServiceClient, id string) string {
-	return c.ServiceURL(version, rootPath, resourcePath, id)
+	return c.ServiceURL(rootPath, resourcePath, id)
 }
diff --git a/openstack/networking/v2/extensions/provider/results.go b/openstack/networking/v2/extensions/provider/results.go
index a20b259..8fc21b4 100755
--- a/openstack/networking/v2/extensions/provider/results.go
+++ b/openstack/networking/v2/extensions/provider/results.go
@@ -1,8 +1,6 @@
 package provider
 
 import (
-	"fmt"
-
 	"github.com/mitchellh/mapstructure"
 	"github.com/rackspace/gophercloud/openstack/networking/v2/networks"
 	"github.com/rackspace/gophercloud/pagination"
@@ -70,14 +68,14 @@
 	if r.Err != nil {
 		return nil, r.Err
 	}
+
 	var res struct {
 		Network *NetworkExtAttrs `json:"network"`
 	}
+
 	err := mapstructure.Decode(r.Resp, &res)
-	if err != nil {
-		return nil, fmt.Errorf("Error decoding Neutron network: %v", err)
-	}
-	return res.Network, nil
+
+	return res.Network, err
 }
 
 // ExtractCreate decorates a CreateResult struct returned from a networks.Create()
@@ -86,14 +84,14 @@
 	if r.Err != nil {
 		return nil, r.Err
 	}
+
 	var res struct {
 		Network *NetworkExtAttrs `json:"network"`
 	}
+
 	err := mapstructure.Decode(r.Resp, &res)
-	if err != nil {
-		return nil, fmt.Errorf("Error decoding Neutron network: %v", err)
-	}
-	return res.Network, nil
+
+	return res.Network, err
 }
 
 // ExtractUpdate decorates a UpdateResult struct returned from a
@@ -102,14 +100,14 @@
 	if r.Err != nil {
 		return nil, r.Err
 	}
+
 	var res struct {
 		Network *NetworkExtAttrs `json:"network"`
 	}
+
 	err := mapstructure.Decode(r.Resp, &res)
-	if err != nil {
-		return nil, fmt.Errorf("Error decoding Neutron network: %v", err)
-	}
-	return res.Network, nil
+
+	return res.Network, err
 }
 
 // ExtractList accepts a Page struct, specifically a NetworkPage struct, and
@@ -121,9 +119,6 @@
 	}
 
 	err := mapstructure.Decode(page.(networks.NetworkPage).Body, &resp)
-	if err != nil {
-		return nil, err
-	}
 
-	return resp.Networks, nil
+	return resp.Networks, err
 }
diff --git a/openstack/networking/v2/extensions/provider/results_test.go b/openstack/networking/v2/extensions/provider/results_test.go
index 74951d9..9801b2e 100644
--- a/openstack/networking/v2/extensions/provider/results_test.go
+++ b/openstack/networking/v2/extensions/provider/results_test.go
@@ -5,17 +5,17 @@
 	"net/http"
 	"testing"
 
+	fake "github.com/rackspace/gophercloud/openstack/networking/v2/common"
 	"github.com/rackspace/gophercloud/openstack/networking/v2/networks"
 	"github.com/rackspace/gophercloud/pagination"
 	th "github.com/rackspace/gophercloud/testhelper"
-	fake "github.com/rackspace/gophercloud/testhelper/client"
 )
 
 func TestList(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 
-	th.Mux.HandleFunc("/networks", func(w http.ResponseWriter, r *http.Request) {
+	th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) {
 		th.TestMethod(t, r, "GET")
 		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
 
@@ -109,7 +109,7 @@
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 
-	th.Mux.HandleFunc("/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) {
+	th.Mux.HandleFunc("/v2.0/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) {
 		th.TestMethod(t, r, "GET")
 		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
 
@@ -150,7 +150,7 @@
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 
-	th.Mux.HandleFunc("/networks", func(w http.ResponseWriter, r *http.Request) {
+	th.Mux.HandleFunc("/v2.0/networks", 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, "Content-Type", "application/json")
@@ -202,7 +202,7 @@
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 
-	th.Mux.HandleFunc("/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) {
+	th.Mux.HandleFunc("/v2.0/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", 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, "Content-Type", "application/json")
diff --git a/openstack/networking/v2/extensions/security/groups/requests_test.go b/openstack/networking/v2/extensions/security/groups/requests_test.go
index 857bdc6..5f074c7 100644
--- a/openstack/networking/v2/extensions/security/groups/requests_test.go
+++ b/openstack/networking/v2/extensions/security/groups/requests_test.go
@@ -5,10 +5,10 @@
 	"net/http"
 	"testing"
 
+	fake "github.com/rackspace/gophercloud/openstack/networking/v2/common"
 	"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules"
 	"github.com/rackspace/gophercloud/pagination"
 	th "github.com/rackspace/gophercloud/testhelper"
-	fake "github.com/rackspace/gophercloud/testhelper/client"
 )
 
 func TestURLs(t *testing.T) {
diff --git a/openstack/networking/v2/extensions/security/groups/results.go b/openstack/networking/v2/extensions/security/groups/results.go
index 6db613e..617d690 100644
--- a/openstack/networking/v2/extensions/security/groups/results.go
+++ b/openstack/networking/v2/extensions/security/groups/results.go
@@ -1,8 +1,6 @@
 package groups
 
 import (
-	"fmt"
-
 	"github.com/mitchellh/mapstructure"
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules"
@@ -40,12 +38,8 @@
 // reached the end of a page and the pager seeks to traverse over a new one. In
 // order to do this, it needs to construct the next page's URL.
 func (p SecGroupPage) NextPageURL() (string, error) {
-	type link struct {
-		Href string `mapstructure:"href"`
-		Rel  string `mapstructure:"rel"`
-	}
 	type resp struct {
-		Links []link `mapstructure:"security_groups_links"`
+		Links []gophercloud.Link `mapstructure:"security_groups_links"`
 	}
 
 	var r resp
@@ -54,17 +48,7 @@
 		return "", err
 	}
 
-	var url string
-	for _, l := range r.Links {
-		if l.Rel == "next" {
-			url = l.Href
-		}
-	}
-	if url == "" {
-		return "", nil
-	}
-
-	return url, nil
+	return gophercloud.ExtractNextURL(r.Links)
 }
 
 // IsEmpty checks whether a SecGroupPage struct is empty.
@@ -85,11 +69,8 @@
 	}
 
 	err := mapstructure.Decode(page.(SecGroupPage).Body, &resp)
-	if err != nil {
-		return nil, err
-	}
 
-	return resp.SecGroups, nil
+	return resp.SecGroups, err
 }
 
 type commonResult struct {
@@ -107,11 +88,8 @@
 	}
 
 	err := mapstructure.Decode(r.Resp, &res)
-	if err != nil {
-		return nil, fmt.Errorf("Error decoding Neutron secgroup: %v", err)
-	}
 
-	return res.SecGroup, nil
+	return res.SecGroup, err
 }
 
 // CreateResult represents the result of a create operation.
diff --git a/openstack/networking/v2/extensions/security/groups/urls.go b/openstack/networking/v2/extensions/security/groups/urls.go
index 2f2bbdd..84f7324 100644
--- a/openstack/networking/v2/extensions/security/groups/urls.go
+++ b/openstack/networking/v2/extensions/security/groups/urls.go
@@ -2,15 +2,12 @@
 
 import "github.com/rackspace/gophercloud"
 
-const (
-	version  = "v2.0"
-	rootPath = "security-groups"
-)
+const rootPath = "security-groups"
 
 func rootURL(c *gophercloud.ServiceClient) string {
-	return c.ServiceURL(version, rootPath)
+	return c.ServiceURL(rootPath)
 }
 
 func resourceURL(c *gophercloud.ServiceClient, id string) string {
-	return c.ServiceURL(version, rootPath, id)
+	return c.ServiceURL(rootPath, id)
 }
diff --git a/openstack/networking/v2/extensions/security/rules/requests_test.go b/openstack/networking/v2/extensions/security/rules/requests_test.go
index a2c7fbb..b5afef3 100644
--- a/openstack/networking/v2/extensions/security/rules/requests_test.go
+++ b/openstack/networking/v2/extensions/security/rules/requests_test.go
@@ -5,9 +5,9 @@
 	"net/http"
 	"testing"
 
+	fake "github.com/rackspace/gophercloud/openstack/networking/v2/common"
 	"github.com/rackspace/gophercloud/pagination"
 	th "github.com/rackspace/gophercloud/testhelper"
-	fake "github.com/rackspace/gophercloud/testhelper/client"
 )
 
 func TestURLs(t *testing.T) {
@@ -165,6 +165,25 @@
 	th.AssertNoErr(t, err)
 }
 
+func TestRequiredCreateOpts(t *testing.T) {
+	res := Create(fake.ServiceClient(), CreateOpts{Direction: "something"})
+	if res.Err == nil {
+		t.Fatalf("Expected error, got none")
+	}
+	res = Create(fake.ServiceClient(), CreateOpts{Direction: DirIngress, EtherType: "something"})
+	if res.Err == nil {
+		t.Fatalf("Expected error, got none")
+	}
+	res = Create(fake.ServiceClient(), CreateOpts{Direction: DirIngress, EtherType: Ether4})
+	if res.Err == nil {
+		t.Fatalf("Expected error, got none")
+	}
+	res = Create(fake.ServiceClient(), CreateOpts{Direction: DirIngress, EtherType: Ether4, SecGroupID: "something", Protocol: "foo"})
+	if res.Err == nil {
+		t.Fatalf("Expected error, got none")
+	}
+}
+
 func TestGet(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
diff --git a/openstack/networking/v2/extensions/security/rules/results.go b/openstack/networking/v2/extensions/security/rules/results.go
index 13d5c7d..ca8435e 100644
--- a/openstack/networking/v2/extensions/security/rules/results.go
+++ b/openstack/networking/v2/extensions/security/rules/results.go
@@ -1,8 +1,6 @@
 package rules
 
 import (
-	"fmt"
-
 	"github.com/mitchellh/mapstructure"
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
@@ -65,12 +63,8 @@
 // reached the end of a page and the pager seeks to traverse over a new one. In
 // order to do this, it needs to construct the next page's URL.
 func (p SecGroupRulePage) NextPageURL() (string, error) {
-	type link struct {
-		Href string `mapstructure:"href"`
-		Rel  string `mapstructure:"rel"`
-	}
 	type resp struct {
-		Links []link `mapstructure:"security_group_rules_links"`
+		Links []gophercloud.Link `mapstructure:"security_group_rules_links"`
 	}
 
 	var r resp
@@ -79,17 +73,7 @@
 		return "", err
 	}
 
-	var url string
-	for _, l := range r.Links {
-		if l.Rel == "next" {
-			url = l.Href
-		}
-	}
-	if url == "" {
-		return "", nil
-	}
-
-	return url, nil
+	return gophercloud.ExtractNextURL(r.Links)
 }
 
 // IsEmpty checks whether a SecGroupRulePage struct is empty.
@@ -110,11 +94,8 @@
 	}
 
 	err := mapstructure.Decode(page.(SecGroupRulePage).Body, &resp)
-	if err != nil {
-		return nil, err
-	}
 
-	return resp.SecGroupRules, nil
+	return resp.SecGroupRules, err
 }
 
 type commonResult struct {
@@ -132,11 +113,8 @@
 	}
 
 	err := mapstructure.Decode(r.Resp, &res)
-	if err != nil {
-		return nil, fmt.Errorf("Error decoding Neutron SecGroupRule: %v", err)
-	}
 
-	return res.SecGroupRule, nil
+	return res.SecGroupRule, err
 }
 
 // CreateResult represents the result of a create operation.
diff --git a/openstack/networking/v2/extensions/security/rules/urls.go b/openstack/networking/v2/extensions/security/rules/urls.go
index 2ffbf37..8e2b2bb 100644
--- a/openstack/networking/v2/extensions/security/rules/urls.go
+++ b/openstack/networking/v2/extensions/security/rules/urls.go
@@ -2,15 +2,12 @@
 
 import "github.com/rackspace/gophercloud"
 
-const (
-	version  = "v2.0"
-	rootPath = "security-group-rules"
-)
+const rootPath = "security-group-rules"
 
 func rootURL(c *gophercloud.ServiceClient) string {
-	return c.ServiceURL(version, rootPath)
+	return c.ServiceURL(rootPath)
 }
 
 func resourceURL(c *gophercloud.ServiceClient, id string) string {
-	return c.ServiceURL(version, rootPath, id)
+	return c.ServiceURL(rootPath, id)
 }
diff --git a/openstack/networking/v2/networks/requests.go b/openstack/networking/v2/networks/requests.go
index 81b1545..39a151a 100644
--- a/openstack/networking/v2/networks/requests.go
+++ b/openstack/networking/v2/networks/requests.go
@@ -6,6 +6,19 @@
 	"github.com/rackspace/gophercloud/pagination"
 )
 
+// AdminState gives users a solid type to work with for create and update
+// operations. It is recommended that users use the `Up` and `Down` enums.
+type AdminState *bool
+
+// Convenience vars for AdminStateUp values.
+var (
+	iTrue  = true
+	iFalse = false
+
+	Up   AdminState = &iTrue
+	Down AdminState = &iFalse
+)
+
 type networkOpts struct {
 	AdminStateUp *bool
 	Name         string
diff --git a/openstack/networking/v2/networks/requests_test.go b/openstack/networking/v2/networks/requests_test.go
index 6b22acd..a263b7b 100644
--- a/openstack/networking/v2/networks/requests_test.go
+++ b/openstack/networking/v2/networks/requests_test.go
@@ -5,16 +5,16 @@
 	"net/http"
 	"testing"
 
+	fake "github.com/rackspace/gophercloud/openstack/networking/v2/common"
 	"github.com/rackspace/gophercloud/pagination"
 	th "github.com/rackspace/gophercloud/testhelper"
-	fake "github.com/rackspace/gophercloud/testhelper/client"
 )
 
 func TestList(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 
-	th.Mux.HandleFunc("/networks", func(w http.ResponseWriter, r *http.Request) {
+	th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) {
 		th.TestMethod(t, r, "GET")
 		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
 
@@ -97,7 +97,7 @@
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 
-	th.Mux.HandleFunc("/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) {
+	th.Mux.HandleFunc("/v2.0/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) {
 		th.TestMethod(t, r, "GET")
 		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
 
@@ -137,7 +137,7 @@
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 
-	th.Mux.HandleFunc("/networks", func(w http.ResponseWriter, r *http.Request) {
+	th.Mux.HandleFunc("/v2.0/networks", 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, "Content-Type", "application/json")
@@ -187,7 +187,7 @@
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 
-	th.Mux.HandleFunc("/networks", func(w http.ResponseWriter, r *http.Request) {
+	th.Mux.HandleFunc("/v2.0/networks", 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, "Content-Type", "application/json")
@@ -216,7 +216,7 @@
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 
-	th.Mux.HandleFunc("/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) {
+	th.Mux.HandleFunc("/v2.0/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", 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, "Content-Type", "application/json")
@@ -264,7 +264,7 @@
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 
-	th.Mux.HandleFunc("/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) {
+	th.Mux.HandleFunc("/v2.0/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) {
 		th.TestMethod(t, r, "DELETE")
 		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
 		w.WriteHeader(http.StatusNoContent)
diff --git a/openstack/networking/v2/networks/results.go b/openstack/networking/v2/networks/results.go
index 2dbd55f..e605fcf 100644
--- a/openstack/networking/v2/networks/results.go
+++ b/openstack/networking/v2/networks/results.go
@@ -1,8 +1,6 @@
 package networks
 
 import (
-	"fmt"
-
 	"github.com/mitchellh/mapstructure"
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
@@ -23,11 +21,8 @@
 	}
 
 	err := mapstructure.Decode(r.Resp, &res)
-	if err != nil {
-		return nil, fmt.Errorf("Error decoding Neutron network: %v", err)
-	}
 
-	return res.Network, nil
+	return res.Network, err
 }
 
 // CreateResult represents the result of a create operation.
@@ -83,12 +78,8 @@
 // the end of a page and the pager seeks to traverse over a new one. In order
 // to do this, it needs to construct the next page's URL.
 func (p NetworkPage) NextPageURL() (string, error) {
-	type link struct {
-		Href string `mapstructure:"href"`
-		Rel  string `mapstructure:"rel"`
-	}
 	type resp struct {
-		Links []link `mapstructure:"networks_links"`
+		Links []gophercloud.Link `mapstructure:"networks_links"`
 	}
 
 	var r resp
@@ -97,17 +88,7 @@
 		return "", err
 	}
 
-	var url string
-	for _, l := range r.Links {
-		if l.Rel == "next" {
-			url = l.Href
-		}
-	}
-	if url == "" {
-		return "", nil
-	}
-
-	return url, nil
+	return gophercloud.ExtractNextURL(r.Links)
 }
 
 // IsEmpty checks whether a NetworkPage struct is empty.
@@ -128,9 +109,6 @@
 	}
 
 	err := mapstructure.Decode(page.(NetworkPage).Body, &resp)
-	if err != nil {
-		return nil, err
-	}
 
-	return resp.Networks, nil
+	return resp.Networks, err
 }
diff --git a/openstack/networking/v2/ports/requests.go b/openstack/networking/v2/ports/requests.go
index 6928d8f..0fbb9f2 100644
--- a/openstack/networking/v2/ports/requests.go
+++ b/openstack/networking/v2/ports/requests.go
@@ -6,6 +6,19 @@
 	"github.com/rackspace/gophercloud/pagination"
 )
 
+// AdminState gives users a solid type to work with for create and update
+// operations. It is recommended that users use the `Up` and `Down` enums.
+type AdminState *bool
+
+// Convenience vars for AdminStateUp values.
+var (
+	iTrue  = true
+	iFalse = false
+
+	Up   AdminState = &iTrue
+	Down AdminState = &iFalse
+)
+
 // 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
diff --git a/openstack/networking/v2/ports/requests_test.go b/openstack/networking/v2/ports/requests_test.go
index 7576341..9e323ef 100644
--- a/openstack/networking/v2/ports/requests_test.go
+++ b/openstack/networking/v2/ports/requests_test.go
@@ -5,16 +5,16 @@
 	"net/http"
 	"testing"
 
+	fake "github.com/rackspace/gophercloud/openstack/networking/v2/common"
 	"github.com/rackspace/gophercloud/pagination"
 	th "github.com/rackspace/gophercloud/testhelper"
-	fake "github.com/rackspace/gophercloud/testhelper/client"
 )
 
 func TestList(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 
-	th.Mux.HandleFunc("/ports", func(w http.ResponseWriter, r *http.Request) {
+	th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) {
 		th.TestMethod(t, r, "GET")
 		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
 
@@ -93,7 +93,7 @@
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 
-	th.Mux.HandleFunc("/ports/46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", func(w http.ResponseWriter, r *http.Request) {
+	th.Mux.HandleFunc("/v2.0/ports/46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", func(w http.ResponseWriter, r *http.Request) {
 		th.TestMethod(t, r, "GET")
 		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
 
@@ -147,7 +147,7 @@
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 
-	th.Mux.HandleFunc("/ports", func(w http.ResponseWriter, r *http.Request) {
+	th.Mux.HandleFunc("/v2.0/ports", 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, "Content-Type", "application/json")
@@ -157,7 +157,14 @@
     "port": {
         "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7",
         "name": "private-port",
-        "admin_state_up": true
+        "admin_state_up": true,
+				"fixed_ips": [
+						{
+								"subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2",
+								"ip_address": "10.0.0.2"
+						}
+				],
+				"security_groups": ["foo"]
     }
 }
 			`)
@@ -193,7 +200,15 @@
 	})
 
 	asu := true
-	options := CreateOpts{Name: "private-port", AdminStateUp: &asu, NetworkID: "a87cc70a-3e15-4acf-8205-9b711a3531b7"}
+	options := CreateOpts{
+		Name:         "private-port",
+		AdminStateUp: &asu,
+		NetworkID:    "a87cc70a-3e15-4acf-8205-9b711a3531b7",
+		FixedIPs: []IP{
+			IP{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"},
+		},
+		SecurityGroups: []string{"foo"},
+	}
 	n, err := Create(fake.ServiceClient(), options).Extract()
 	th.AssertNoErr(t, err)
 
@@ -211,11 +226,18 @@
 	th.AssertDeepEquals(t, n.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"})
 }
 
+func TestRequiredCreateOpts(t *testing.T) {
+	res := Create(fake.ServiceClient(), CreateOpts{})
+	if res.Err == nil {
+		t.Fatalf("Expected error, got none")
+	}
+}
+
 func TestUpdate(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 
-	th.Mux.HandleFunc("/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) {
+	th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", 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, "Content-Type", "application/json")
@@ -288,7 +310,7 @@
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 
-	th.Mux.HandleFunc("/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) {
+	th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) {
 		th.TestMethod(t, r, "DELETE")
 		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
 		w.WriteHeader(http.StatusNoContent)
diff --git a/openstack/networking/v2/ports/results.go b/openstack/networking/v2/ports/results.go
index 91118a4..cedd658 100644
--- a/openstack/networking/v2/ports/results.go
+++ b/openstack/networking/v2/ports/results.go
@@ -1,8 +1,6 @@
 package ports
 
 import (
-	"fmt"
-
 	"github.com/mitchellh/mapstructure"
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
@@ -23,11 +21,8 @@
 	}
 
 	err := mapstructure.Decode(r.Resp, &res)
-	if err != nil {
-		return nil, fmt.Errorf("Error decoding Neutron port: %v", err)
-	}
 
-	return res.Port, nil
+	return res.Port, err
 }
 
 // CreateResult represents the result of a create operation.
@@ -94,10 +89,7 @@
 // to do this, it needs to construct the next page's URL.
 func (p PortPage) NextPageURL() (string, error) {
 	type resp struct {
-		Links []struct {
-			Href string `mapstructure:"href"`
-			Rel  string `mapstructure:"rel"`
-		} `mapstructure:"ports_links"`
+		Links []gophercloud.Link `mapstructure:"ports_links"`
 	}
 
 	var r resp
@@ -106,17 +98,7 @@
 		return "", err
 	}
 
-	var url string
-	for _, l := range r.Links {
-		if l.Rel == "next" {
-			url = l.Href
-		}
-	}
-	if url == "" {
-		return "", nil
-	}
-
-	return url, nil
+	return gophercloud.ExtractNextURL(r.Links)
 }
 
 // IsEmpty checks whether a PortPage struct is empty.
@@ -137,9 +119,6 @@
 	}
 
 	err := mapstructure.Decode(page.(PortPage).Body, &resp)
-	if err != nil {
-		return nil, err
-	}
 
-	return resp.Ports, nil
+	return resp.Ports, err
 }
diff --git a/openstack/networking/v2/subnets/requests.go b/openstack/networking/v2/subnets/requests.go
index 092c914..a7e6b53 100644
--- a/openstack/networking/v2/subnets/requests.go
+++ b/openstack/networking/v2/subnets/requests.go
@@ -6,6 +6,19 @@
 	"github.com/rackspace/gophercloud/pagination"
 )
 
+// AdminState gives users a solid type to work with for create and update
+// operations. It is recommended that users use the `Up` and `Down` enums.
+type AdminState *bool
+
+// Convenience vars for AdminStateUp values.
+var (
+	iTrue  = true
+	iFalse = false
+
+	Up   AdminState = &iTrue
+	Down AdminState = &iFalse
+)
+
 // 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
@@ -76,7 +89,7 @@
 	IPVersion       int
 	EnableDHCP      *bool
 	DNSNameservers  []string
-	HostRoutes      []interface{}
+	HostRoutes      []HostRoute
 }
 
 // Create accepts a CreateOpts struct and creates a new subnet using the values
@@ -108,7 +121,7 @@
 		IPVersion       int              `json:"ip_version,omitempty"`
 		EnableDHCP      *bool            `json:"enable_dhcp,omitempty"`
 		DNSNameservers  []string         `json:"dns_nameservers,omitempty"`
-		HostRoutes      []interface{}    `json:"host_routes,omitempty"`
+		HostRoutes      []HostRoute      `json:"host_routes,omitempty"`
 	}
 	type request struct {
 		Subnet subnet `json:"subnet"`
@@ -151,7 +164,7 @@
 	Name           string
 	GatewayIP      string
 	DNSNameservers []string
-	HostRoutes     []interface{}
+	HostRoutes     []HostRoute
 	EnableDHCP     *bool
 }
 
@@ -159,11 +172,11 @@
 // 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     []interface{} `json:"host_routes,omitempty"`
-		EnableDHCP     *bool         `json:"enable_dhcp,omitempty"`
+		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"`
diff --git a/openstack/networking/v2/subnets/requests_test.go b/openstack/networking/v2/subnets/requests_test.go
index 9f3e8df..987064a 100644
--- a/openstack/networking/v2/subnets/requests_test.go
+++ b/openstack/networking/v2/subnets/requests_test.go
@@ -5,16 +5,16 @@
 	"net/http"
 	"testing"
 
+	fake "github.com/rackspace/gophercloud/openstack/networking/v2/common"
 	"github.com/rackspace/gophercloud/pagination"
 	th "github.com/rackspace/gophercloud/testhelper"
-	fake "github.com/rackspace/gophercloud/testhelper/client"
 )
 
 func TestList(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 
-	th.Mux.HandleFunc("/subnets", func(w http.ResponseWriter, r *http.Request) {
+	th.Mux.HandleFunc("/v2.0/subnets", func(w http.ResponseWriter, r *http.Request) {
 		th.TestMethod(t, r, "GET")
 		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
 
@@ -128,7 +128,7 @@
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 
-	th.Mux.HandleFunc("/subnets/54d6f61d-db07-451c-9ab3-b9609b6b6f0b", func(w http.ResponseWriter, r *http.Request) {
+	th.Mux.HandleFunc("/v2.0/subnets/54d6f61d-db07-451c-9ab3-b9609b6b6f0b", func(w http.ResponseWriter, r *http.Request) {
 		th.TestMethod(t, r, "GET")
 		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
 
@@ -184,7 +184,7 @@
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 
-	th.Mux.HandleFunc("/subnets", func(w http.ResponseWriter, r *http.Request) {
+	th.Mux.HandleFunc("/v2.0/subnets", 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, "Content-Type", "application/json")
@@ -194,7 +194,15 @@
     "subnet": {
         "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
         "ip_version": 4,
-        "cidr": "192.168.199.0/24"
+        "cidr": "192.168.199.0/24",
+				"dns_nameservers": ["foo"],
+				"allocation_pools": [
+						{
+								"start": "192.168.199.2",
+								"end": "192.168.199.254"
+						}
+				],
+				"host_routes": [{"destination":"","nexthop": "bar"}]
     }
 }
 			`)
@@ -226,7 +234,21 @@
 		`)
 	})
 
-	opts := CreateOpts{NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a22", IPVersion: 4, CIDR: "192.168.199.0/24"}
+	opts := CreateOpts{
+		NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a22",
+		IPVersion: 4,
+		CIDR:      "192.168.199.0/24",
+		AllocationPools: []AllocationPool{
+			AllocationPool{
+				Start: "192.168.199.2",
+				End:   "192.168.199.254",
+			},
+		},
+		DNSNameservers: []string{"foo"},
+		HostRoutes: []HostRoute{
+			HostRoute{NextHop: "bar"},
+		},
+	}
 	s, err := Create(fake.ServiceClient(), opts).Extract()
 	th.AssertNoErr(t, err)
 
@@ -248,11 +270,28 @@
 	th.AssertEquals(t, s.ID, "3b80198d-4f7b-4f77-9ef5-774d54e17126")
 }
 
+func TestRequiredCreateOpts(t *testing.T) {
+	res := Create(fake.ServiceClient(), CreateOpts{})
+	if res.Err == nil {
+		t.Fatalf("Expected error, got none")
+	}
+
+	res = Create(fake.ServiceClient(), CreateOpts{NetworkID: "foo"})
+	if res.Err == nil {
+		t.Fatalf("Expected error, got none")
+	}
+
+	res = Create(fake.ServiceClient(), CreateOpts{NetworkID: "foo", CIDR: "bar", IPVersion: 40})
+	if res.Err == nil {
+		t.Fatalf("Expected error, got none")
+	}
+}
+
 func TestUpdate(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 
-	th.Mux.HandleFunc("/subnets/08eae331-0402-425a-923c-34f7cfe39c1b", func(w http.ResponseWriter, r *http.Request) {
+	th.Mux.HandleFunc("/v2.0/subnets/08eae331-0402-425a-923c-34f7cfe39c1b", 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, "Content-Type", "application/json")
@@ -260,7 +299,9 @@
 		th.TestJSONRequest(t, r, `
 {
     "subnet": {
-        "name": "my_new_subnet"
+        "name": "my_new_subnet",
+				"dns_nameservers": ["foo"],
+				"host_routes": [{"destination":"","nexthop": "bar"}]
     }
 }
 		`)
@@ -292,7 +333,13 @@
 	`)
 	})
 
-	opts := UpdateOpts{Name: "my_new_subnet"}
+	opts := UpdateOpts{
+		Name:           "my_new_subnet",
+		DNSNameservers: []string{"foo"},
+		HostRoutes: []HostRoute{
+			HostRoute{NextHop: "bar"},
+		},
+	}
 	s, err := Update(fake.ServiceClient(), "08eae331-0402-425a-923c-34f7cfe39c1b", opts).Extract()
 	th.AssertNoErr(t, err)
 
@@ -304,7 +351,7 @@
 	th.SetupHTTP()
 	defer th.TeardownHTTP()
 
-	th.Mux.HandleFunc("/subnets/08eae331-0402-425a-923c-34f7cfe39c1b", func(w http.ResponseWriter, r *http.Request) {
+	th.Mux.HandleFunc("/v2.0/subnets/08eae331-0402-425a-923c-34f7cfe39c1b", func(w http.ResponseWriter, r *http.Request) {
 		th.TestMethod(t, r, "DELETE")
 		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
 		w.WriteHeader(http.StatusNoContent)
diff --git a/openstack/networking/v2/subnets/results.go b/openstack/networking/v2/subnets/results.go
index 5c5744c..3c9ef71 100644
--- a/openstack/networking/v2/subnets/results.go
+++ b/openstack/networking/v2/subnets/results.go
@@ -1,8 +1,6 @@
 package subnets
 
 import (
-	"fmt"
-
 	"github.com/mitchellh/mapstructure"
 	"github.com/rackspace/gophercloud"
 	"github.com/rackspace/gophercloud/pagination"
@@ -23,11 +21,8 @@
 	}
 
 	err := mapstructure.Decode(r.Resp, &res)
-	if err != nil {
-		return nil, fmt.Errorf("Error decoding Neutron subnet: %v", err)
-	}
 
-	return res.Subnet, nil
+	return res.Subnet, err
 }
 
 // CreateResult represents the result of a create operation.
@@ -99,12 +94,8 @@
 // the end of a page and the pager seeks to traverse over a new one. In order
 // to do this, it needs to construct the next page's URL.
 func (p SubnetPage) NextPageURL() (string, error) {
-	type link struct {
-		Href string `mapstructure:"href"`
-		Rel  string `mapstructure:"rel"`
-	}
 	type resp struct {
-		Links []link `mapstructure:"subnets_links"`
+		Links []gophercloud.Link `mapstructure:"subnets_links"`
 	}
 
 	var r resp
@@ -113,17 +104,7 @@
 		return "", err
 	}
 
-	var url string
-	for _, l := range r.Links {
-		if l.Rel == "next" {
-			url = l.Href
-		}
-	}
-	if url == "" {
-		return "", nil
-	}
-
-	return url, nil
+	return gophercloud.ExtractNextURL(r.Links)
 }
 
 // IsEmpty checks whether a SubnetPage struct is empty.
@@ -144,9 +125,6 @@
 	}
 
 	err := mapstructure.Decode(page.(SubnetPage).Body, &resp)
-	if err != nil {
-		return nil, err
-	}
 
-	return resp.Subnets, nil
+	return resp.Subnets, err
 }
diff --git a/results.go b/results.go
index f7d526c..647ba46 100644
--- a/results.go
+++ b/results.go
@@ -10,3 +10,28 @@
 
 // RFC3339Milli describes a time format used by API responses.
 const RFC3339Milli = "2006-01-02T15:04:05.999999Z"
+
+// Link represents a structure that enables paginated collections how to
+// traverse backward or forward. The "Rel" field is usually either "next".
+type Link struct {
+	Href string `mapstructure:"href"`
+	Rel  string `mapstructure:"rel"`
+}
+
+// ExtractNextURL attempts to extract the next URL from a JSON structure. It
+// follows the common structure of nesting back and next links.
+func ExtractNextURL(links []Link) (string, error) {
+	var url string
+
+	for _, l := range links {
+		if l.Rel == "next" {
+			url = l.Href
+		}
+	}
+
+	if url == "" {
+		return "", nil
+	}
+
+	return url, nil
+}