error types for networks v2
diff --git a/openstack/networking/v2/extensions/layer3/floatingips/requests.go b/openstack/networking/v2/extensions/layer3/floatingips/requests.go
index 155bdbe..54477b8 100644
--- a/openstack/networking/v2/extensions/layer3/floatingips/requests.go
+++ b/openstack/networking/v2/extensions/layer3/floatingips/requests.go
@@ -1,8 +1,6 @@
 package floatingips
 
 import (
-	"fmt"
-
 	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/pagination"
 )
@@ -39,6 +37,12 @@
 	})
 }
 
+// CreateOptsBuilder is the interface type must satisfy to be used as Create
+// options.
+type CreateOptsBuilder interface {
+	ToFloatingIPCreateMap() (map[string]interface{}, error)
+}
+
 // CreateOpts contains all the values needed to create a new floating IP
 // resource. The only required fields are FloatingNetworkID and PortID which
 // refer to the external network and internal port respectively.
@@ -50,9 +54,37 @@
 	TenantID          string
 }
 
-var (
-	errFloatingNetworkIDRequired = fmt.Errorf("A NetworkID is required")
-)
+// ToFloatingIPCreateMap allows CreateOpts to satisfy the CreateOptsBuilder
+// interface
+func (opts CreateOpts) ToFloatingIPCreateMap() (map[string]interface{}, error) {
+	if opts.FloatingNetworkID == "" {
+		err := gophercloud.ErrMissingInput{}
+		err.Function = "floatingips.ToFloatingIPCreateMap"
+		err.Argument = "floatingips.CreateOpts.FloatingNetworkID"
+		return nil, err
+	}
+
+	i := map[string]string{
+		"floating_network_id": opts.FloatingNetworkID,
+	}
+
+	if opts.FloatingIP != "" {
+		i["floating_ip_address"] = opts.FloatingIP
+	}
+	if opts.PortID != "" {
+		i["port_id"] = opts.PortID
+	}
+	if opts.FixedIP != "" {
+		i["fixed_ip_address"] = opts.FixedIP
+	}
+	if opts.TenantID != "" {
+		i["tenant_id"] = opts.TenantID
+	}
+
+	b := make(map[string]interface{})
+	b["floatingip"] = i
+	return b, nil
+}
 
 // Create accepts a CreateOpts struct and uses the values provided to create a
 // new floating IP resource. You can create floating IPs on external networks
@@ -78,38 +110,17 @@
 // operation will fail and return a 400 error code. If the PortID and FixedIP
 // are already associated with another resource, the operation will fail and
 // returns a 409 error code.
-func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
-	var res CreateResult
+func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
+	var r CreateResult
 
-	// Validate
-	if opts.FloatingNetworkID == "" {
-		res.Err = errFloatingNetworkIDRequired
-		return res
+	b, err := opts.ToFloatingIPCreateMap()
+	if err != nil {
+		r.Err = err
+		return r
 	}
 
-	// Define structures
-	type floatingIP struct {
-		FloatingNetworkID string `json:"floating_network_id"`
-		FloatingIP        string `json:"floating_ip_address,omitempty"`
-		PortID            string `json:"port_id,omitempty"`
-		FixedIP           string `json:"fixed_ip_address,omitempty"`
-		TenantID          string `json:"tenant_id,omitempty"`
-	}
-	type request struct {
-		FloatingIP floatingIP `json:"floatingip"`
-	}
-
-	// Populate request body
-	reqBody := request{FloatingIP: floatingIP{
-		FloatingNetworkID: opts.FloatingNetworkID,
-		FloatingIP:        opts.FloatingIP,
-		PortID:            opts.PortID,
-		FixedIP:           opts.FixedIP,
-		TenantID:          opts.TenantID,
-	}}
-
-	_, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil)
-	return res
+	_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
+	return r
 }
 
 // Get retrieves a particular floating IP resource based on its unique ID.
diff --git a/openstack/networking/v2/extensions/layer3/routers/requests.go b/openstack/networking/v2/extensions/layer3/routers/requests.go
index 4215889..910760f 100644
--- a/openstack/networking/v2/extensions/layer3/routers/requests.go
+++ b/openstack/networking/v2/extensions/layer3/routers/requests.go
@@ -1,8 +1,6 @@
 package routers
 
 import (
-	"errors"
-
 	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/pagination"
 )
@@ -153,7 +151,11 @@
 	return res
 }
 
-var errInvalidInterfaceOpts = errors.New("When adding a router interface you must provide either a subnet ID or a port ID")
+// InterfaceOptsBuilder is what types must satisfy to be used as AddInterface
+// options.
+type InterfaceOptsBuilder interface {
+	ToInterfaceAddMap() (map[string]interface{}, error)
+}
 
 // InterfaceOpts allow you to work with operations that either add or remote
 // an internal interface from a router.
@@ -162,6 +164,29 @@
 	PortID   string
 }
 
+// ToInterfaceAddMap allows InterfaceOpts to satisfy the InterfaceOptsBuilder
+// interface
+func (opts InterfaceOpts) ToInterfaceAddMap() (map[string]interface{}, error) {
+	if (opts.SubnetID == "" && opts.PortID == "") || (opts.SubnetID != "" && opts.PortID != "") {
+		err := gophercloud.ErrInvalidInput{}
+		err.Function = "routers.ToInterfaceAddMap"
+		err.Argument = "routers.InterfaceOpts.SubnetID/routers.InterfaceOpts.PortID"
+		err.Info = "When adding a router interface, you must provide one and only" +
+			" one of a subnet ID or a port ID"
+		return nil, err
+	}
+
+	b := make(map[string]interface{})
+	if opts.SubnetID != "" {
+		b["subnet_id"] = opts.SubnetID
+	}
+	if opts.PortID != "" {
+		b["port_id"] = opts.PortID
+	}
+
+	return b, nil
+}
+
 // AddInterface attaches a subnet to an internal router interface. You must
 // specify either a SubnetID or PortID in the request body. If you specify both,
 // the operation will fail and an error will be returned.
@@ -183,27 +208,28 @@
 // identifier of a new port created by this operation. After the operation
 // completes, the device ID of the port is set to the router ID, and the
 // device owner attribute is set to `network:router_interface'.
-func AddInterface(c *gophercloud.ServiceClient, id string, opts InterfaceOpts) InterfaceResult {
-	var res InterfaceResult
+func AddInterface(c *gophercloud.ServiceClient, id string, opts InterfaceOptsBuilder) InterfaceResult {
+	var r InterfaceResult
 
-	// Validate
-	if (opts.SubnetID == "" && opts.PortID == "") || (opts.SubnetID != "" && opts.PortID != "") {
-		res.Err = errInvalidInterfaceOpts
-		return res
+	if id == "" {
+		err := gophercloud.ErrMissingInput{}
+		err.Function = "routers.AddInterface"
+		err.Argument = "id"
+		r.Err = err
+		return r
 	}
 
-	type request struct {
-		SubnetID string `json:"subnet_id,omitempty"`
-		PortID   string `json:"port_id,omitempty"`
+	b, err := opts.ToInterfaceAddMap()
+	if err != nil {
+		r.Err = err
+		return r
 	}
 
-	body := request{SubnetID: opts.SubnetID, PortID: opts.PortID}
-
-	_, res.Err = c.Put(addInterfaceURL(c, id), body, &res.Body, &gophercloud.RequestOpts{
+	_, r.Err = c.Put(addInterfaceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
 		OkCodes: []int{200},
 	})
 
-	return res
+	return r
 }
 
 // RemoveInterface removes an internal router interface, which detaches a
diff --git a/openstack/networking/v2/extensions/lbaas/monitors/requests.go b/openstack/networking/v2/extensions/lbaas/monitors/requests.go
index 3a967bd..d1a7942 100644
--- a/openstack/networking/v2/extensions/lbaas/monitors/requests.go
+++ b/openstack/networking/v2/extensions/lbaas/monitors/requests.go
@@ -56,15 +56,11 @@
 	TypeHTTPS = "HTTPS"
 )
 
-var (
-	errValidTypeRequired     = fmt.Errorf("A valid Type is required. Supported values are PING, TCP, HTTP and HTTPS")
-	errDelayRequired         = fmt.Errorf("Delay is required")
-	errTimeoutRequired       = fmt.Errorf("Timeout is required")
-	errMaxRetriesRequired    = fmt.Errorf("MaxRetries is required")
-	errURLPathRequired       = fmt.Errorf("URL path is required")
-	errExpectedCodesRequired = fmt.Errorf("ExpectedCodes is required")
-	errDelayMustGETimeout    = fmt.Errorf("Delay must be greater than or equal to timeout")
-)
+// CreateOptsBuilder is what types must satisfy to be used as Create
+// options.
+type CreateOptsBuilder interface {
+	ToLBMonitorCreateMap() (map[string]interface{}, error)
+}
 
 // CreateOpts contains all the values needed to create a new health monitor.
 type CreateOpts struct {
@@ -102,6 +98,94 @@
 	AdminStateUp *bool
 }
 
+// ToLBMonitorCreateMap allows CreateOpts to satisfy the CreateOptsBuilder
+// interface
+func (opts CreateOpts) ToLBMonitorCreateMap() (map[string]interface{}, error) {
+	allowed := map[string]bool{TypeHTTP: true, TypeHTTPS: true, TypeTCP: true, TypePING: true}
+	if opts.Type == "" || allowed[opts.Type] == false {
+		err := gophercloud.ErrInvalidInput{}
+		err.Function = "monitors.ToLBMonitorCreateMap"
+		err.Argument = "monitors.CreateOpts.Type"
+		err.Info = "Supported values are PING, TCP, HTTP and HTTPS"
+		return nil, err
+	}
+	if opts.Delay == 0 {
+		err := gophercloud.ErrMissingInput{}
+		err.Function = "monitors.ToLBMonitorCreateMap"
+		err.Argument = "monitors.CreateOpts.Delay"
+		return nil, err
+	}
+	if opts.Timeout == 0 {
+		err := gophercloud.ErrMissingInput{}
+		err.Function = "monitors.ToLBMonitorCreateMap"
+		err.Argument = "monitors.CreateOpts.Timeout"
+		return nil, err
+	}
+	if opts.MaxRetries == 0 {
+		err := gophercloud.ErrMissingInput{}
+		err.Function = "monitors.ToLBMonitorCreateMap"
+		err.Argument = "monitors.CreateOpts.MaxRetries"
+		return nil, err
+	}
+
+	if opts.Type == TypeHTTP || opts.Type == TypeHTTPS {
+		if opts.URLPath == "" {
+			err := gophercloud.ErrMissingInput{}
+			err.Function = "monitors.ToLBMonitorCreateMap"
+			err.Argument = "monitors.CreateOpts.URLPath"
+			return nil, err
+		}
+		if opts.ExpectedCodes == "" {
+			err := gophercloud.ErrMissingInput{}
+			err.Function = "monitors.ToLBMonitorCreateMap"
+			err.Argument = "monitors.CreateOpts.ExpectedCodes"
+			return nil, err
+		}
+	}
+	if opts.Delay < opts.Timeout {
+		err := gophercloud.ErrInvalidInput{}
+		err.Function = "monitors.ToLBMonitorCreateMap"
+		err.Argument = "monitors.CreateOpts.Delay/monitors.CreateOpts.Timeout"
+		err.Info = "Delay must be greater than or equal to timeout"
+		return nil, err
+	}
+
+	i := map[string]interface{}{
+		"type":    opts.Type,
+		"delay":   opts.Delay,
+		"timeout": opts.Timeout,
+	}
+
+	if opts.MaxRetries != 0 {
+		i["max_retries"] = opts.MaxRetries
+	}
+
+	if opts.TenantID != "" {
+		i["tenant_id"] = opts.TenantID
+	}
+
+	if opts.URLPath != "" {
+		i["url_path"] = opts.URLPath
+	}
+
+	if opts.ExpectedCodes != "" {
+		i["expected_codes"] = opts.ExpectedCodes
+	}
+
+	if opts.HTTPMethod != "" {
+		i["http_method"] = opts.HTTPMethod
+	}
+
+	if opts.AdminStateUp != nil {
+		i["admin_state_up"] = &opts.AdminStateUp
+	}
+
+	b := make(map[string]interface{})
+	b["health_monitor"] = i
+
+	return b, nil
+}
+
 // Create is an operation which provisions a new health monitor. There are
 // different types of monitor you can provision: PING, TCP or HTTP(S). Below
 // are examples of how to create each one.
@@ -116,68 +200,17 @@
 // CreateOpts{Type: TypeHTTP, Delay: 20, Timeout: 10, MaxRetries: 3,
 //  HttpMethod: "HEAD", ExpectedCodes: "200"}
 //
-func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
-	var res CreateResult
+func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
+	var r CreateResult
 
-	// Validate inputs
-	allowed := map[string]bool{TypeHTTP: true, TypeHTTPS: true, TypeTCP: true, TypePING: true}
-	if opts.Type == "" || allowed[opts.Type] == false {
-		res.Err = errValidTypeRequired
-	}
-	if opts.Delay == 0 {
-		res.Err = errDelayRequired
-	}
-	if opts.Timeout == 0 {
-		res.Err = errTimeoutRequired
-	}
-	if opts.MaxRetries == 0 {
-		res.Err = errMaxRetriesRequired
-	}
-	if opts.Type == TypeHTTP || opts.Type == TypeHTTPS {
-		if opts.URLPath == "" {
-			res.Err = errURLPathRequired
-		}
-		if opts.ExpectedCodes == "" {
-			res.Err = errExpectedCodesRequired
-		}
-	}
-	if opts.Delay < opts.Timeout {
-		res.Err = errDelayMustGETimeout
-	}
-	if res.Err != nil {
-		return res
+	b, err := opts.ToLBMonitorCreateMap()
+	if err != nil {
+		r.Err = err
+		return r
 	}
 
-	type monitor struct {
-		Type          string  `json:"type"`
-		Delay         int     `json:"delay"`
-		Timeout       int     `json:"timeout"`
-		MaxRetries    int     `json:"max_retries"`
-		TenantID      *string `json:"tenant_id,omitempty"`
-		URLPath       *string `json:"url_path,omitempty"`
-		ExpectedCodes *string `json:"expected_codes,omitempty"`
-		HTTPMethod    *string `json:"http_method,omitempty"`
-		AdminStateUp  *bool   `json:"admin_state_up,omitempty"`
-	}
-
-	type request struct {
-		Monitor monitor `json:"health_monitor"`
-	}
-
-	reqBody := request{Monitor: monitor{
-		Type:          opts.Type,
-		Delay:         opts.Delay,
-		Timeout:       opts.Timeout,
-		MaxRetries:    opts.MaxRetries,
-		TenantID:      gophercloud.MaybeString(opts.TenantID),
-		URLPath:       gophercloud.MaybeString(opts.URLPath),
-		ExpectedCodes: gophercloud.MaybeString(opts.ExpectedCodes),
-		HTTPMethod:    gophercloud.MaybeString(opts.HTTPMethod),
-		AdminStateUp:  opts.AdminStateUp,
-	}}
-
-	_, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil)
-	return res
+	_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
+	return r
 }
 
 // Get retrieves a particular health monitor based on its unique ID.
@@ -187,6 +220,12 @@
 	return res
 }
 
+// UpdateOptsBuilder is what types must satisfy to be used as Update
+// options.
+type UpdateOptsBuilder interface {
+	ToLBMonitorUpdateMap() (map[string]interface{}, error)
+}
+
 // UpdateOpts contains all the values needed to update an existing virtual IP.
 // Attributes not listed here but appear in CreateOpts are immutable and cannot
 // be updated.
@@ -218,43 +257,71 @@
 	AdminStateUp *bool
 }
 
-// Update is an operation which modifies the attributes of the specified monitor.
-func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult {
-	var res UpdateResult
-
+// ToLBMonitorUpdateMap allows UpdateOpts to satisfy the UpdateOptsBuilder
+// interface
+func (opts UpdateOpts) ToLBMonitorUpdateMap() (map[string]interface{}, error) {
 	if opts.Delay > 0 && opts.Timeout > 0 && opts.Delay < opts.Timeout {
-		res.Err = errDelayMustGETimeout
+		err := gophercloud.ErrInvalidInput{}
+		err.Function = "monitors.ToLBMonitorCreateMap"
+		err.Argument = "monitors.CreateOpts.Delay/monitors.CreateOpts.Timeout"
+		err.Value = fmt.Sprintf("%d/%d", opts.Delay, opts.Timeout)
+		err.Info = "Delay must be greater than or equal to timeout"
+		return nil, err
 	}
 
-	type monitor struct {
-		Delay         int     `json:"delay"`
-		Timeout       int     `json:"timeout"`
-		MaxRetries    int     `json:"max_retries"`
-		URLPath       *string `json:"url_path,omitempty"`
-		ExpectedCodes *string `json:"expected_codes,omitempty"`
-		HTTPMethod    *string `json:"http_method,omitempty"`
-		AdminStateUp  *bool   `json:"admin_state_up,omitempty"`
+	i := map[string]interface{}{
+		"delay":       opts.Delay,
+		"timeout":     opts.Timeout,
+		"max_retries": opts.MaxRetries,
 	}
 
-	type request struct {
-		Monitor monitor `json:"health_monitor"`
+	if opts.URLPath != "" {
+		i["url_path"] = opts.URLPath
 	}
 
-	reqBody := request{Monitor: monitor{
-		Delay:         opts.Delay,
-		Timeout:       opts.Timeout,
-		MaxRetries:    opts.MaxRetries,
-		URLPath:       gophercloud.MaybeString(opts.URLPath),
-		ExpectedCodes: gophercloud.MaybeString(opts.ExpectedCodes),
-		HTTPMethod:    gophercloud.MaybeString(opts.HTTPMethod),
-		AdminStateUp:  opts.AdminStateUp,
-	}}
+	if opts.ExpectedCodes != "" {
+		i["expected_codes"] = opts.ExpectedCodes
+	}
 
-	_, res.Err = c.Put(resourceURL(c, id), reqBody, &res.Body, &gophercloud.RequestOpts{
+	if opts.HTTPMethod != "" {
+		i["http_method"] = opts.HTTPMethod
+	}
+
+	if opts.AdminStateUp != nil {
+		i["admin_state_up"] = *opts.AdminStateUp
+	}
+
+	b := make(map[string]interface{})
+	b["health_monitor"] = i
+
+	fmt.Printf("b: %+v\n", b)
+
+	return b, nil
+}
+
+// Update is an operation which modifies the attributes of the specified monitor.
+func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
+	var r UpdateResult
+
+	if id == "" {
+		err := gophercloud.ErrMissingInput{}
+		err.Function = "monitors.Update"
+		err.Argument = "id"
+		r.Err = err
+		return r
+	}
+
+	b, err := opts.ToLBMonitorUpdateMap()
+	if err != nil {
+		r.Err = err
+		return r
+	}
+
+	_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
 		OkCodes: []int{200, 202},
 	})
 
-	return res
+	return r
 }
 
 // Delete will permanently delete a particular monitor based on its unique ID.
diff --git a/openstack/networking/v2/extensions/lbaas/monitors/requests_test.go b/openstack/networking/v2/extensions/lbaas/monitors/requests_test.go
index d4ee6e9..6f1a976 100644
--- a/openstack/networking/v2/extensions/lbaas/monitors/requests_test.go
+++ b/openstack/networking/v2/extensions/lbaas/monitors/requests_test.go
@@ -8,6 +8,7 @@
 	fake "github.com/gophercloud/gophercloud/openstack/networking/v2/common"
 	"github.com/gophercloud/gophercloud/pagination"
 	th "github.com/gophercloud/gophercloud/testhelper"
+	"github.com/jrperritt/gophercloud"
 )
 
 func TestURLs(t *testing.T) {
@@ -251,11 +252,12 @@
 		th.TestJSONRequest(t, r, `
 {
    "health_monitor":{
-      "delay": 3,
+      "delay": 30,
       "timeout": 20,
       "max_retries": 10,
       "url_path": "/another_check",
-      "expected_codes": "301"
+      "expected_codes": "301",
+			"admin_state_up": true
    }
 }
 			`)
@@ -268,7 +270,7 @@
     "health_monitor": {
         "admin_state_up": true,
         "tenant_id": "4fd44f30292945e481c7b8a0c8908869",
-        "delay": 3,
+        "delay": 30,
         "max_retries": 10,
         "http_method": "GET",
         "timeout": 20,
@@ -287,11 +289,12 @@
 	})
 
 	_, err := Update(fake.ServiceClient(), "b05e44b5-81f9-4551-b474-711a722698f7", UpdateOpts{
-		Delay:         3,
+		Delay:         30,
 		Timeout:       20,
 		MaxRetries:    10,
 		URLPath:       "/another_check",
 		ExpectedCodes: "301",
+		AdminStateUp:  gophercloud.Enabled,
 	}).Extract()
 
 	th.AssertNoErr(t, err)
diff --git a/openstack/networking/v2/extensions/lbaas/vips/requests.go b/openstack/networking/v2/extensions/lbaas/vips/requests.go
index 18b2ee7..0f99fe8 100644
--- a/openstack/networking/v2/extensions/lbaas/vips/requests.go
+++ b/openstack/networking/v2/extensions/lbaas/vips/requests.go
@@ -1,8 +1,6 @@
 package vips
 
 import (
-	"fmt"
-
 	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/pagination"
 )
@@ -60,13 +58,11 @@
 	})
 }
 
-var (
-	errNameRequired         = fmt.Errorf("Name is required")
-	errSubnetIDRequried     = fmt.Errorf("SubnetID is required")
-	errProtocolRequired     = fmt.Errorf("Protocol is required")
-	errProtocolPortRequired = fmt.Errorf("Protocol port is required")
-	errPoolIDRequired       = fmt.Errorf("PoolID is required")
-)
+// CreateOptsBuilder is what types must satisfy to be used as Create
+// options.
+type CreateOptsBuilder interface {
+	ToVIPCreateMap() (map[string]interface{}, error)
+}
 
 // CreateOpts contains all the values needed to create a new virtual IP.
 type CreateOpts struct {
@@ -107,6 +103,72 @@
 	AdminStateUp *bool
 }
 
+// ToVIPCreateMap allows CreateOpts to satisfy the CreateOptsBuilder
+// interface
+func (opts CreateOpts) ToVIPCreateMap() (map[string]interface{}, error) {
+	if opts.Name == "" {
+		err := gophercloud.ErrMissingInput{}
+		err.Function = "vips.ToVIPCreateMap"
+		err.Argument = "vips.CreateOpts.Name"
+		return nil, err
+	}
+	if opts.SubnetID == "" {
+		err := gophercloud.ErrMissingInput{}
+		err.Function = "vips.ToVIPCreateMap"
+		err.Argument = "vips.CreateOpts.SubnetID"
+		return nil, err
+	}
+	if opts.Protocol == "" {
+		err := gophercloud.ErrMissingInput{}
+		err.Function = "vips.ToVIPCreateMap"
+		err.Argument = "vips.CreateOpts.Protocol"
+		return nil, err
+	}
+	if opts.ProtocolPort == 0 {
+		err := gophercloud.ErrMissingInput{}
+		err.Function = "vips.ToVIPCreateMap"
+		err.Argument = "vips.CreateOpts.ProtocolPort"
+		return nil, err
+	}
+	if opts.PoolID == "" {
+		err := gophercloud.ErrMissingInput{}
+		err.Function = "vips.ToVIPCreateMap"
+		err.Argument = "vips.CreateOpts.PoolID"
+		return nil, err
+	}
+
+	i := map[string]interface{}{
+		"name":          opts.Name,
+		"subnet_id":     opts.SubnetID,
+		"protocol":      opts.Protocol,
+		"protocol_port": opts.ProtocolPort,
+		"pool_id":       opts.PoolID,
+	}
+	if opts.Description != "" {
+		i["description"] = opts.Description
+	}
+	if opts.TenantID != "" {
+		i["tenant_id"] = opts.TenantID
+	}
+	if opts.Address != "" {
+		i["address"] = opts.Address
+	}
+	if opts.Persistence != nil {
+		i["session_persistence"] = opts.Persistence
+	}
+	if opts.ConnLimit != nil {
+		i["connection_limit"] = opts.ConnLimit
+	}
+	if opts.AdminStateUp != nil {
+		i["admin_state_up"] = opts.AdminStateUp
+	}
+
+	b := make(map[string]interface{})
+	b["vip"] = i
+
+	return b, nil
+}
+
 // Create is an operation which provisions a new virtual IP based on the
 // configuration defined in the CreateOpts struct. Once the request is
 // validated and progress has started on the provisioning process, a
@@ -119,67 +181,16 @@
 // Users with an admin role can create VIPs on behalf of other tenants by
 // specifying a TenantID attribute different than their own.
 func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
-	var res CreateResult
+	var r CreateResult
 
-	// Validate required opts
-	if opts.Name == "" {
-		res.Err = errNameRequired
-		return res
-	}
-	if opts.SubnetID == "" {
-		res.Err = errSubnetIDRequried
-		return res
-	}
-	if opts.Protocol == "" {
-		res.Err = errProtocolRequired
-		return res
-	}
-	if opts.ProtocolPort == 0 {
-		res.Err = errProtocolPortRequired
-		return res
-	}
-	if opts.PoolID == "" {
-		res.Err = errPoolIDRequired
-		return res
+	b, err := opts.ToVIPCreateMap()
+	if err != nil {
+		r.Err = err
+		return r
 	}
 
-	type vip struct {
-		Name         string              `json:"name"`
-		SubnetID     string              `json:"subnet_id"`
-		Protocol     string              `json:"protocol"`
-		ProtocolPort int                 `json:"protocol_port"`
-		PoolID       string              `json:"pool_id"`
-		Description  *string             `json:"description,omitempty"`
-		TenantID     *string             `json:"tenant_id,omitempty"`
-		Address      *string             `json:"address,omitempty"`
-		Persistence  *SessionPersistence `json:"session_persistence,omitempty"`
-		ConnLimit    *int                `json:"connection_limit,omitempty"`
-		AdminStateUp *bool               `json:"admin_state_up,omitempty"`
-	}
-
-	type request struct {
-		VirtualIP vip `json:"vip"`
-	}
-
-	reqBody := request{VirtualIP: vip{
-		Name:         opts.Name,
-		SubnetID:     opts.SubnetID,
-		Protocol:     opts.Protocol,
-		ProtocolPort: opts.ProtocolPort,
-		PoolID:       opts.PoolID,
-		Description:  gophercloud.MaybeString(opts.Description),
-		TenantID:     gophercloud.MaybeString(opts.TenantID),
-		Address:      gophercloud.MaybeString(opts.Address),
-		ConnLimit:    opts.ConnLimit,
-		AdminStateUp: opts.AdminStateUp,
-	}}
-
-	if opts.Persistence != nil {
-		reqBody.VirtualIP.Persistence = opts.Persistence
-	}
-
-	_, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil)
-	return res
+	_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
+	return r
 }
 
 // Get retrieves a particular virtual IP based on its unique ID.
diff --git a/openstack/networking/v2/extensions/security/groups/requests.go b/openstack/networking/v2/extensions/security/groups/requests.go
index f9e252c..9adce66 100644
--- a/openstack/networking/v2/extensions/security/groups/requests.go
+++ b/openstack/networking/v2/extensions/security/groups/requests.go
@@ -1,8 +1,6 @@
 package groups
 
 import (
-	"fmt"
-
 	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/pagination"
 )
@@ -36,10 +34,6 @@
 	})
 }
 
-var (
-	errNameRequired = fmt.Errorf("Name is required")
-)
-
 // CreateOpts contains all the values needed to create a new security group.
 type CreateOpts struct {
 	// Required. Human-readable name for the VIP. Does not have to be unique.
@@ -59,7 +53,10 @@
 
 	// Validate required opts
 	if opts.Name == "" {
-		res.Err = errNameRequired
+		err := gophercloud.ErrMissingInput{}
+		err.Function = "groups.Create"
+		err.Argument = "groups.CreateOpts.Name"
+		res.Err = err
 		return res
 	}
 
@@ -99,33 +96,47 @@
 
 // IDFromName is a convenience function that returns a security group's ID given its name.
 func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
-	securityGroupCount := 0
-	securityGroupID := ""
+	count := 0
+	id := ""
 	if name == "" {
-		return "", fmt.Errorf("A security group name must be provided.")
+		err := &gophercloud.ErrMissingInput{}
+		err.Function = "groups.IDFromName"
+		err.Argument = "name"
+		return "", err
 	}
-	pager := List(client, ListOpts{})
-	pager.EachPage(func(page pagination.Page) (bool, error) {
-		securityGroupList, err := ExtractGroups(page)
-		if err != nil {
-			return false, err
-		}
 
-		for _, s := range securityGroupList {
-			if s.Name == name {
-				securityGroupCount++
-				securityGroupID = s.ID
-			}
-		}
-		return true, nil
-	})
+	pages, err := List(client, ListOpts{}).AllPages()
+	if err != nil {
+		return "", err
+	}
 
-	switch securityGroupCount {
+	all, err := ExtractGroups(pages)
+	if err != nil {
+		return "", err
+	}
+
+	for _, s := range all {
+		if s.Name == name {
+			count++
+			id = s.ID
+		}
+	}
+
+	switch count {
 	case 0:
-		return "", fmt.Errorf("Unable to find security group: %s", name)
+		err := &gophercloud.ErrResourceNotFound{}
+		err.Name = name
+		err.ResourceType = "group"
+		err.Function = "groups.IDFromName"
+		return "", err
 	case 1:
-		return securityGroupID, nil
+		return id, nil
 	default:
-		return "", fmt.Errorf("Found %d security groups matching %s", securityGroupCount, name)
+		err := &gophercloud.ErrMultipleResourcesFound{}
+		err.Count = count
+		err.Name = name
+		err.ResourceType = "group"
+		err.Function = "groups.IDFromName"
+		return "", err
 	}
 }
diff --git a/openstack/networking/v2/extensions/security/rules/requests.go b/openstack/networking/v2/extensions/security/rules/requests.go
index 07083c2..2da6227 100644
--- a/openstack/networking/v2/extensions/security/rules/requests.go
+++ b/openstack/networking/v2/extensions/security/rules/requests.go
@@ -1,8 +1,6 @@
 package rules
 
 import (
-	"fmt"
-
 	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/pagination"
 )
@@ -43,14 +41,6 @@
 	})
 }
 
-// Errors
-var (
-	errValidDirectionRequired = fmt.Errorf("A valid Direction is required")
-	errValidEtherTypeRequired = fmt.Errorf("A valid EtherType is required")
-	errSecGroupIDRequired     = fmt.Errorf("A valid SecGroupID is required")
-	errValidProtocolRequired  = fmt.Errorf("A valid Protocol is required")
-)
-
 // Constants useful for CreateOpts
 const (
 	DirIngress   = "ingress"
@@ -62,6 +52,12 @@
 	ProtocolICMP = "icmp"
 )
 
+// CreateOptsBuilder is what types must satisfy to be used as Create
+// options.
+type CreateOptsBuilder interface {
+	ToSecGroupRuleCreateMap() (map[string]interface{}, error)
+}
+
 // CreateOpts contains all the values needed to create a new security group rule.
 type CreateOpts struct {
 	// Required. Must be either "ingress" or "egress": the direction in which the
@@ -104,59 +100,75 @@
 	TenantID string
 }
 
-// Create is an operation which adds a new security group rule and associates it
-// with an existing security group (whose ID is specified in CreateOpts).
-func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
-	var res CreateResult
-
-	// Validate required opts
+// ToSecGroupRuleCreateMap allows CreateOpts to satisfy the CreateOptsBuilder
+// interface
+func (opts CreateOpts) ToSecGroupRuleCreateMap() (map[string]interface{}, error) {
 	if opts.Direction != DirIngress && opts.Direction != DirEgress {
-		res.Err = errValidDirectionRequired
-		return res
+		err := gophercloud.ErrMissingInput{}
+		err.Function = "rules.ToSecGroupRuleCreateMap"
+		err.Argument = "rules.CreateOpts.Direction"
+		return nil, err
 	}
 	if opts.EtherType != Ether4 && opts.EtherType != Ether6 {
-		res.Err = errValidEtherTypeRequired
-		return res
+		err := gophercloud.ErrMissingInput{}
+		err.Function = "rules.ToSecGroupRuleCreateMap"
+		err.Argument = "rules.CreateOpts.EtherType"
+		return nil, err
 	}
 	if opts.SecGroupID == "" {
-		res.Err = errSecGroupIDRequired
-		return res
+		err := gophercloud.ErrMissingInput{}
+		err.Function = "rules.ToSecGroupRuleCreateMap"
+		err.Argument = "rules.CreateOpts.SecGroupID"
+		return nil, err
 	}
 	if opts.Protocol != "" && opts.Protocol != ProtocolTCP && opts.Protocol != ProtocolUDP && opts.Protocol != ProtocolICMP {
-		res.Err = errValidProtocolRequired
-		return res
+		err := gophercloud.ErrMissingInput{}
+		err.Function = "rules.ToSecGroupRuleCreateMap"
+		err.Argument = "rules.CreateOpts.Protocol"
+		return nil, err
 	}
 
-	type secrule struct {
-		Direction      string `json:"direction"`
-		EtherType      string `json:"ethertype"`
-		SecGroupID     string `json:"security_group_id"`
-		PortRangeMax   int    `json:"port_range_max,omitempty"`
-		PortRangeMin   int    `json:"port_range_min,omitempty"`
-		Protocol       string `json:"protocol,omitempty"`
-		RemoteGroupID  string `json:"remote_group_id,omitempty"`
-		RemoteIPPrefix string `json:"remote_ip_prefix,omitempty"`
-		TenantID       string `json:"tenant_id,omitempty"`
+	i := map[string]interface{}{
+		"direction":         opts.Direction,
+		"ethertype":         opts.EtherType,
+		"security_group_id": opts.SecGroupID,
+	}
+	if opts.PortRangeMax != 0 {
+		i["port_range_max"] = opts.PortRangeMax
+	}
+	if opts.PortRangeMin != 0 {
+		i["port_range_min"] = opts.PortRangeMin
+	}
+	if opts.Protocol != "" {
+		i["protocol"] = opts.Protocol
+	}
+	if opts.RemoteGroupID != "" {
+		i["remote_group_id"] = opts.RemoteGroupID
+	}
+	if opts.RemoteIPPrefix != "" {
+		i["remote_ip_prefix"] = opts.RemoteIPPrefix
+	}
+	if opts.TenantID != "" {
+		i["tenant_id"] = opts.TenantID
 	}
 
-	type request struct {
-		SecRule secrule `json:"security_group_rule"`
+	b := make(map[string]interface{})
+	b["security_group_rule"] = i
+	return b, nil
+}
+
+// Create is an operation which adds a new security group rule and associates it
+// with an existing security group (whose ID is specified in CreateOpts).
+func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
+	var r CreateResult
+
+	b, err := opts.ToSecGroupRuleCreateMap()
+	if err != nil {
+		r.Err = err
+		return r
 	}
-
-	reqBody := request{SecRule: secrule{
-		Direction:      opts.Direction,
-		EtherType:      opts.EtherType,
-		SecGroupID:     opts.SecGroupID,
-		PortRangeMax:   opts.PortRangeMax,
-		PortRangeMin:   opts.PortRangeMin,
-		Protocol:       opts.Protocol,
-		RemoteGroupID:  opts.RemoteGroupID,
-		RemoteIPPrefix: opts.RemoteIPPrefix,
-		TenantID:       opts.TenantID,
-	}}
-
-	_, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil)
-	return res
+	_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
+	return r
 }
 
 // Get retrieves a particular security group rule based on its unique ID.
diff --git a/openstack/networking/v2/networks/requests.go b/openstack/networking/v2/networks/requests.go
index 1cb1878..03fac14 100644
--- a/openstack/networking/v2/networks/requests.go
+++ b/openstack/networking/v2/networks/requests.go
@@ -1,8 +1,6 @@
 package networks
 
 import (
-	"fmt"
-
 	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/pagination"
 )
@@ -194,33 +192,47 @@
 
 // IDFromName is a convenience function that returns a network's ID given its name.
 func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
-	networkCount := 0
-	networkID := ""
+	count := 0
+	id := ""
 	if name == "" {
-		return "", fmt.Errorf("A network name must be provided.")
+		err := &gophercloud.ErrMissingInput{}
+		err.Function = "networks.IDFromName"
+		err.Argument = "name"
+		return "", err
 	}
-	pager := List(client, nil)
-	pager.EachPage(func(page pagination.Page) (bool, error) {
-		networkList, err := ExtractNetworks(page)
-		if err != nil {
-			return false, err
-		}
 
-		for _, n := range networkList {
-			if n.Name == name {
-				networkCount++
-				networkID = n.ID
-			}
-		}
-		return true, nil
-	})
+	pages, err := List(client, nil).AllPages()
+	if err != nil {
+		return "", err
+	}
 
-	switch networkCount {
+	all, err := ExtractNetworks(pages)
+	if err != nil {
+		return "", err
+	}
+
+	for _, s := range all {
+		if s.Name == name {
+			count++
+			id = s.ID
+		}
+	}
+
+	switch count {
 	case 0:
-		return "", fmt.Errorf("Unable to find network: %s", name)
+		err := &gophercloud.ErrResourceNotFound{}
+		err.Name = name
+		err.ResourceType = "network"
+		err.Function = "networks.IDFromName"
+		return "", err
 	case 1:
-		return networkID, nil
+		return id, nil
 	default:
-		return "", fmt.Errorf("Found %d networks matching %s", networkCount, name)
+		err := &gophercloud.ErrMultipleResourcesFound{}
+		err.Count = count
+		err.Name = name
+		err.ResourceType = "network"
+		err.Function = "networks.IDFromName"
+		return "", err
 	}
 }
diff --git a/openstack/networking/v2/ports/requests.go b/openstack/networking/v2/ports/requests.go
index 4730882..d719f93 100644
--- a/openstack/networking/v2/ports/requests.go
+++ b/openstack/networking/v2/ports/requests.go
@@ -1,8 +1,6 @@
 package ports
 
 import (
-	"fmt"
-
 	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/pagination"
 )
@@ -236,33 +234,47 @@
 
 // IDFromName is a convenience function that returns a port's ID given its name.
 func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
-	portCount := 0
-	portID := ""
+	count := 0
+	id := ""
 	if name == "" {
-		return "", fmt.Errorf("A port name must be provided.")
+		err := &gophercloud.ErrMissingInput{}
+		err.Function = "ports.IDFromName"
+		err.Argument = "name"
+		return "", err
 	}
-	pager := List(client, nil)
-	pager.EachPage(func(page pagination.Page) (bool, error) {
-		portList, err := ExtractPorts(page)
-		if err != nil {
-			return false, err
-		}
 
-		for _, p := range portList {
-			if p.Name == name {
-				portCount++
-				portID = p.ID
-			}
-		}
-		return true, nil
-	})
+	pages, err := List(client, nil).AllPages()
+	if err != nil {
+		return "", err
+	}
 
-	switch portCount {
+	all, err := ExtractPorts(pages)
+	if err != nil {
+		return "", err
+	}
+
+	for _, s := range all {
+		if s.Name == name {
+			count++
+			id = s.ID
+		}
+	}
+
+	switch count {
 	case 0:
-		return "", fmt.Errorf("Unable to find port: %s", name)
+		err := &gophercloud.ErrResourceNotFound{}
+		err.Name = name
+		err.ResourceType = "port"
+		err.Function = "ports.IDFromName"
+		return "", err
 	case 1:
-		return portID, nil
+		return id, nil
 	default:
-		return "", fmt.Errorf("Found %d ports matching %s", portCount, name)
+		err := &gophercloud.ErrMultipleResourcesFound{}
+		err.Count = count
+		err.Name = name
+		err.ResourceType = "port"
+		err.Function = "ports.IDFromName"
+		return "", err
 	}
 }
diff --git a/openstack/networking/v2/subnets/requests.go b/openstack/networking/v2/subnets/requests.go
index 78fa9e2..e55a034 100644
--- a/openstack/networking/v2/subnets/requests.go
+++ b/openstack/networking/v2/subnets/requests.go
@@ -1,8 +1,6 @@
 package subnets
 
 import (
-	"fmt"
-
 	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/pagination"
 )
@@ -239,33 +237,47 @@
 
 // IDFromName is a convenience function that returns a subnet's ID given its name.
 func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
-	subnetCount := 0
-	subnetID := ""
+	count := 0
+	id := ""
 	if name == "" {
-		return "", fmt.Errorf("A subnet name must be provided.")
+		err := &gophercloud.ErrMissingInput{}
+		err.Function = "subnets.IDFromName"
+		err.Argument = "name"
+		return "", err
 	}
-	pager := List(client, nil)
-	pager.EachPage(func(page pagination.Page) (bool, error) {
-		subnetList, err := ExtractSubnets(page)
-		if err != nil {
-			return false, err
-		}
 
-		for _, s := range subnetList {
-			if s.Name == name {
-				subnetCount++
-				subnetID = s.ID
-			}
-		}
-		return true, nil
-	})
+	pages, err := List(client, nil).AllPages()
+	if err != nil {
+		return "", err
+	}
 
-	switch subnetCount {
+	all, err := ExtractSubnets(pages)
+	if err != nil {
+		return "", err
+	}
+
+	for _, s := range all {
+		if s.Name == name {
+			count++
+			id = s.ID
+		}
+	}
+
+	switch count {
 	case 0:
-		return "", fmt.Errorf("Unable to find subnet: %s", name)
+		err := &gophercloud.ErrResourceNotFound{}
+		err.Name = name
+		err.ResourceType = "subnet"
+		err.Function = "subnets.IDFromName"
+		return "", err
 	case 1:
-		return subnetID, nil
+		return id, nil
 	default:
-		return "", fmt.Errorf("Found %d subnets matching %s", subnetCount, name)
+		err := &gophercloud.ErrMultipleResourcesFound{}
+		err.Count = count
+		err.Name = name
+		err.ResourceType = "subnet"
+		err.Function = "subnets.IDFromName"
+		return "", err
 	}
 }