Replace struct based mapping by manual mapping

The old way does not allow to handle updates correctly. When a nullable
field is set and we want to remove the value we need to be able to set
a null value in the json request body. For instance this happen in
firewall rules for field source_ip_address (among others).
diff --git a/openstack/networking/v2/extensions/fwaas/firewalls/requests.go b/openstack/networking/v2/extensions/fwaas/firewalls/requests.go
index 2c44713..950c14d 100644
--- a/openstack/networking/v2/extensions/fwaas/firewalls/requests.go
+++ b/openstack/networking/v2/extensions/fwaas/firewalls/requests.go
@@ -44,31 +44,45 @@
 	TenantID     string
 	Name         string
 	Description  string
-	AdminStateUp bool
-	Shared       bool
+	AdminStateUp *bool
+	Shared       *bool
 	PolicyID     string
 }
 
+// ToPolicyCreateMap casts a CreateOpts struct to a map.
+func (opts CreateOpts) ToPolicyCreateMap() map[string]interface{} {
+	p := make(map[string]interface{})
+
+	if opts.TenantID != "" {
+		p["tenant_id"] = opts.TenantID
+	}
+	if opts.Name != "" {
+		p["name"] = opts.Name
+	}
+	if opts.Description != "" {
+		p["description"] = opts.Description
+	}
+	if opts.Shared != nil {
+		p["shared"] = *opts.Shared
+	}
+	if opts.AdminStateUp != nil {
+		p["admin_state_up"] = *opts.AdminStateUp
+	}
+	if opts.PolicyID != "" {
+		p["firewall_policy_id"] = opts.PolicyID
+	}
+
+	return p
+}
+
 // Create accepts a CreateOpts struct and uses the values to create a new firewall
 func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
-	type firewall struct {
-		TenantID     string `json:"tenant_id,omitempty"`
-		Name         string `json:"name,omitempty"`
-		Description  string `json:"description,omitempty"`
-		AdminStateUp bool   `json:"admin_state_up,omitempty"`
-		PolicyID     string `json:"firewall_policy_id"`
-	}
+
 	type request struct {
-		Firewall firewall `json:"firewall"`
+		Firewall map[string]interface{} `json:"firewall"`
 	}
 
-	reqBody := request{Firewall: firewall{
-		TenantID:     opts.TenantID,
-		Name:         opts.Name,
-		Description:  opts.Description,
-		AdminStateUp: opts.AdminStateUp,
-		PolicyID:     opts.PolicyID,
-	}}
+	reqBody := request{Firewall: opts.ToPolicyCreateMap()}
 
 	var res CreateResult
 	_, res.Err = perigee.Request("POST", rootURL(c), perigee.Options{
@@ -94,31 +108,44 @@
 // UpdateOpts contains the values used when updating a firewall.
 type UpdateOpts struct {
 	// Name of the firewall.
-	Name         string
-	Description  string
-	AdminStateUp bool
-	Status       string
+	Name         *string
+	Description  *string
+	AdminStateUp *bool
+	Shared       *bool
 	PolicyID     string
 }
 
+// ToPolicyUpdateMap casts a CreateOpts struct to a map.
+func (opts UpdateOpts) ToPolicyUpdateMap() map[string]interface{} {
+	p := make(map[string]interface{})
+
+	if opts.Name != nil {
+		p["name"] = *opts.Name
+	}
+	if opts.Description != nil {
+		p["description"] = *opts.Description
+	}
+	if opts.Shared != nil {
+		p["shared"] = *opts.Shared
+	}
+	if opts.AdminStateUp != nil {
+		p["admin_state_up"] = *opts.AdminStateUp
+	}
+	if opts.PolicyID != "" {
+		p["firewall_policy_id"] = opts.PolicyID
+	}
+
+	return p
+}
+
 // Update allows firewalls to be updated.
 func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult {
-	type firewall struct {
-		Name         string `json:"name"`
-		Description  string `json:"description"`
-		AdminStateUp bool   `json:"admin_state_up,omitempty"`
-		PolicyID     string `json:"firewall_policy_id,omitempty"`
-	}
+
 	type request struct {
-		Firewall firewall `json:"firewall"`
+		Firewall map[string]interface{} `json:"firewall"`
 	}
 
-	reqBody := request{Firewall: firewall{
-		Name:         opts.Name,
-		Description:  opts.Description,
-		AdminStateUp: opts.AdminStateUp,
-		PolicyID:     opts.PolicyID,
-	}}
+	reqBody := request{Firewall: opts.ToPolicyUpdateMap()}
 
 	// Send request to API
 	var res UpdateResult
diff --git a/openstack/networking/v2/extensions/fwaas/firewalls/results.go b/openstack/networking/v2/extensions/fwaas/firewalls/results.go
index 53c3029..a8c76ee 100644
--- a/openstack/networking/v2/extensions/fwaas/firewalls/results.go
+++ b/openstack/networking/v2/extensions/fwaas/firewalls/results.go
@@ -13,6 +13,7 @@
 	AdminStateUp bool   `json:"admin_state_up" mapstructure:"admin_state_up"`
 	Status       string `json:"status" mapstructure:"status"`
 	PolicyID     string `json:"firewall_policy_id" mapstructure:"firewall_policy_id"`
+	TenantID     string `json:"tenant_id" mapstructure:"tenant_id"`
 }
 
 type commonResult struct {
diff --git a/openstack/networking/v2/extensions/fwaas/policies/requests.go b/openstack/networking/v2/extensions/fwaas/policies/requests.go
index 5242f3a..260b728 100644
--- a/openstack/networking/v2/extensions/fwaas/policies/requests.go
+++ b/openstack/networking/v2/extensions/fwaas/policies/requests.go
@@ -10,6 +10,8 @@
 	TenantID    string `q:"tenant_id"`
 	Name        string `q:"name"`
 	Description string `q:"description"`
+	Shared      bool   `q:"shared"`
+	Audited     bool   `q:"audited"`
 	ID          string `q:"id"`
 	Limit       int    `q:"limit"`
 	Marker      string `q:"marker"`
@@ -38,30 +40,48 @@
 type CreateOpts struct {
 	// Only required if the caller has an admin role and wants to create a firewall policy
 	// for another tenant.
-	TenantId    string
+	TenantID    string
 	Name        string
 	Description string
+	Shared      *bool
+	Audited     *bool
 	Rules       []string
 }
 
+// ToPolicyCreateMap casts a CreateOpts struct to a map.
+func (opts CreateOpts) ToPolicyCreateMap() map[string]interface{} {
+	p := make(map[string]interface{})
+
+	if opts.TenantID != "" {
+		p["tenant_id"] = opts.TenantID
+	}
+	if opts.Name != "" {
+		p["name"] = opts.Name
+	}
+	if opts.Description != "" {
+		p["description"] = opts.Description
+	}
+	if opts.Shared != nil {
+		p["shared"] = *opts.Shared
+	}
+	if opts.Audited != nil {
+		p["audited"] = *opts.Audited
+	}
+	if opts.Rules != nil {
+		p["firewall_rules"] = opts.Rules
+	}
+
+	return p
+}
+
 // Create accepts a CreateOpts struct and uses the values to create a new firewall policy
 func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
-	type policy struct {
-		TenantId    string   `json:"tenant_id,omitempty"`
-		Name        string   `json:"name,omitempty"`
-		Description string   `json:"description,omitempty"`
-		Rules       []string `json:"firewall_rules,omitempty"`
-	}
+
 	type request struct {
-		Policy policy `json:"firewall_policy"`
+		Policy map[string]interface{} `json:"firewall_policy"`
 	}
 
-	reqBody := request{Policy: policy{
-		TenantId:    opts.TenantId,
-		Name:        opts.Name,
-		Description: opts.Description,
-		Rules:       opts.Rules,
-	}}
+	reqBody := request{Policy: opts.ToPolicyCreateMap()}
 
 	var res CreateResult
 	_, res.Err = perigee.Request("POST", rootURL(c), perigee.Options{
@@ -87,27 +107,44 @@
 // UpdateOpts contains the values used when updating a firewall policy.
 type UpdateOpts struct {
 	// Name of the firewall policy.
-	Name        string
-	Description string
+	Name        *string
+	Description *string
+	Shared      *bool
+	Audited     *bool
 	Rules       []string
 }
 
+// ToPolicyUpdateMap casts a CreateOpts struct to a map.
+func (opts UpdateOpts) ToPolicyUpdateMap() map[string]interface{} {
+	p := make(map[string]interface{})
+
+	if opts.Name != nil {
+		p["name"] = *opts.Name
+	}
+	if opts.Description != nil {
+		p["description"] = *opts.Description
+	}
+	if opts.Shared != nil {
+		p["shared"] = *opts.Shared
+	}
+	if opts.Audited != nil {
+		p["audited"] = *opts.Audited
+	}
+	if opts.Rules != nil {
+		p["firewall_rules"] = opts.Rules
+	}
+
+	return p
+}
+
 // Update allows firewall policies to be updated.
 func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult {
-	type policy struct {
-		Name        string   `json:"name"`
-		Description string   `json:"description"`
-		Rules       []string `json:"firewall_rules,omitempty"`
-	}
+
 	type request struct {
-		Policy policy `json:"firewall_policy"`
+		Policy map[string]interface{} `json:"firewall_policy"`
 	}
 
-	reqBody := request{Policy: policy{
-		Name:        opts.Name,
-		Description: opts.Description,
-		Rules:       opts.Rules,
-	}}
+	reqBody := request{Policy: opts.ToPolicyUpdateMap()}
 
 	// Send request to API
 	var res UpdateResult
diff --git a/openstack/networking/v2/extensions/fwaas/rules/requests.go b/openstack/networking/v2/extensions/fwaas/rules/requests.go
index 181a758..18690b7 100644
--- a/openstack/networking/v2/extensions/fwaas/rules/requests.go
+++ b/openstack/networking/v2/extensions/fwaas/rules/requests.go
@@ -49,14 +49,14 @@
 
 // CreateOpts contains all the values needed to create a new firewall rule.
 type CreateOpts struct {
-	// Mandatory
+	// Mandatory for create
 	Protocol string
 	Action   string
 	// Optional
 	TenantID             string
 	Name                 string
 	Description          string
-	IPVersion            *int
+	IPVersion            int
 	SourceIPAddress      string
 	DestinationIPAddress string
 	SourcePort           string
@@ -65,41 +65,55 @@
 	Enabled              *bool
 }
 
+// ToRuleCreateMap casts a CreateOpts struct to a map.
+func (opts CreateOpts) ToRuleCreateMap() map[string]interface{} {
+	r := make(map[string]interface{})
+
+	r["protocol"] = opts.Protocol
+	r["action"] = opts.Action
+
+	if opts.TenantID != "" {
+		r["tenant_id"] = opts.TenantID
+	}
+	if opts.Name != "" {
+		r["name"] = opts.Name
+	}
+	if opts.Description != "" {
+		r["description"] = opts.Description
+	}
+	if opts.IPVersion != 0 {
+		r["ip_version"] = opts.IPVersion
+	}
+	if opts.SourceIPAddress != "" {
+		r["source_ip_address"] = opts.SourceIPAddress
+	}
+	if opts.DestinationIPAddress != "" {
+		r["destination_ip_address"] = opts.DestinationIPAddress
+	}
+	if opts.SourcePort != "" {
+		r["source_port"] = opts.SourcePort
+	}
+	if opts.DestinationPort != "" {
+		r["destination_port"] = opts.DestinationPort
+	}
+	if opts.Shared != nil {
+		r["shared"] = *opts.Shared
+	}
+	if opts.Enabled != nil {
+		r["enabled"] = *opts.Enabled
+	}
+
+	return r
+}
+
 // Create accepts a CreateOpts struct and uses the values to create a new firewall rule
 func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
 
-	type rule struct {
-		TenantID             string `json:"tenant_id,omitempty"`
-		Name                 string `json:"name"`
-		Description          string `json:"description"`
-		Protocol             string `json:"protocol"`
-		Action               string `json:"action"`
-		IPVersion            *int   `json:"ip_version,omitempty"`
-		SourceIPAddress      string `json:"source_ip_address,omitempty"`
-		DestinationIPAddress string `json:"destination_ip_address,omitempty"`
-		SourcePort           string `json:"source_port,omitempty"`
-		DestinationPort      string `json:"destination_port,omitempty"`
-		Shared               *bool  `json:"shared,omitempty"`
-		Enabled              *bool  `json:"enabled,omitempty"`
-	}
 	type request struct {
-		Rule rule `json:"firewall_rule"`
+		Rule map[string]interface{} `json:"firewall_rule"`
 	}
 
-	reqBody := request{Rule: rule{
-		TenantID:             opts.TenantID,
-		Name:                 opts.Name,
-		Description:          opts.Description,
-		Protocol:             opts.Protocol,
-		Action:               opts.Action,
-		IPVersion:            opts.IPVersion,
-		SourceIPAddress:      opts.SourceIPAddress,
-		DestinationIPAddress: opts.DestinationIPAddress,
-		SourcePort:           opts.SourcePort,
-		DestinationPort:      opts.DestinationPort,
-		Shared:               opts.Shared,
-		Enabled:              opts.Enabled,
-	}}
+	reqBody := request{Rule: opts.ToRuleCreateMap()}
 
 	var res CreateResult
 	_, res.Err = perigee.Request("POST", rootURL(c), perigee.Options{
@@ -123,52 +137,90 @@
 }
 
 // UpdateOpts contains the values used when updating a firewall rule.
+// Optional
 type UpdateOpts struct {
-	Name                 string
-	Description          string
 	Protocol             string
 	Action               string
-	IPVersion            *int
-	SourceIPAddress      string
-	DestinationIPAddress string
-	SourcePort           string
-	DestinationPort      string
+	Name                 *string
+	Description          *string
+	IPVersion            int
+	SourceIPAddress      *string
+	DestinationIPAddress *string
+	SourcePort           *string
+	DestinationPort      *string
 	Shared               *bool
 	Enabled              *bool
 }
 
-// Update allows firewall policies to be updated.
-func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult {
-	type rule struct {
-		Name                 string `json:"name"`
-		Description          string `json:"description"`
-		Protocol             string `json:"protocol"`
-		Action               string `json:"action"`
-		IPVersion            *int   `json:"ip_version,omitempty"`
-		SourceIPAddress      string `json:"source_ip_address"`
-		DestinationIPAddress string `json:"destination_ip_address"`
-		SourcePort           string `json:"source_port"`
-		DestinationPort      string `json:"destination_port"`
-		Shared               *bool  `json:"shared,omitempty"`
-		Enabled              *bool  `json:"enabled,omitempty"`
+// ToRuleUpdateMap casts a UpdateOpts struct to a map.
+func (opts UpdateOpts) ToRuleUpdateMap() map[string]interface{} {
+	r := make(map[string]interface{})
+
+	if opts.Protocol != "" {
+		r["protocol"] = opts.Protocol
 	}
-	type request struct {
-		Rule rule `json:"firewall_rule"`
+	if opts.Action != "" {
+		r["action"] = opts.Action
+	}
+	if opts.Name != nil {
+		r["name"] = *opts.Name
+	}
+	if opts.Description != nil {
+		r["description"] = *opts.Description
+	}
+	if opts.IPVersion != 0 {
+		r["ip_version"] = opts.IPVersion
+	}
+	if opts.SourceIPAddress != nil {
+		s := *opts.SourceIPAddress
+		if s == "" {
+			r["source_ip_address"] = nil
+		} else {
+			r["source_ip_address"] = s
+		}
+	}
+	if opts.DestinationIPAddress != nil {
+		s := *opts.DestinationIPAddress
+		if s == "" {
+			r["destination_ip_address"] = nil
+		} else {
+			r["destination_ip_address"] = s
+		}
+	}
+	if opts.SourcePort != nil {
+		s := *opts.SourcePort
+		if s == "" {
+			r["source_port"] = nil
+		} else {
+			r["source_port"] = s
+		}
+	}
+	if opts.DestinationPort != nil {
+		s := *opts.DestinationPort
+		if s == "" {
+			r["destination_port"] = nil
+		} else {
+			r["destination_port"] = s
+		}
+	}
+	if opts.Shared != nil {
+		r["shared"] = *opts.Shared
+	}
+	if opts.Enabled != nil {
+		r["enabled"] = *opts.Enabled
 	}
 
-	reqBody := request{Rule: rule{
-		Name:                 opts.Name,
-		Description:          opts.Description,
-		Protocol:             opts.Protocol,
-		Action:               opts.Action,
-		IPVersion:            opts.IPVersion,
-		SourceIPAddress:      opts.SourceIPAddress,
-		DestinationIPAddress: opts.DestinationIPAddress,
-		SourcePort:           opts.SourcePort,
-		DestinationPort:      opts.DestinationPort,
-		Shared:               opts.Shared,
-		Enabled:              opts.Enabled,
-	}}
+	return r
+}
+
+// Update allows firewall policies to be updated.
+func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult {
+
+	type request struct {
+		Rule map[string]interface{} `json:"firewall_rule"`
+	}
+
+	reqBody := request{Rule: opts.ToRuleUpdateMap()}
 
 	// Send request to API
 	var res UpdateResult
diff --git a/openstack/networking/v2/extensions/fwaas/rules/results.go b/openstack/networking/v2/extensions/fwaas/rules/results.go
index ff3de8a..d772024 100644
--- a/openstack/networking/v2/extensions/fwaas/rules/results.go
+++ b/openstack/networking/v2/extensions/fwaas/rules/results.go
@@ -20,6 +20,9 @@
 	DestinationPort      string `json:"destination_port,omitempty" mapstructure:"destination_port"`
 	Shared               bool   `json:"shared,omitempty" mapstructure:"shared"`
 	Enabled              bool   `json:"enabled,omitempty" mapstructure:"enabled"`
+	PolicyID             string `json:"firewall_policy_id" mapstructure:"firewall_policy_id"`
+	Position             int    `json:"position" mapstructure:"position"`
+	TenantID             string `json:"tenant_id" mapstructure:"tenant_id"`
 }
 
 // RulePage is the page returned by a pager when traversing over a