Repository: hetznercloud/hcloud-go
Pull request #633 (2025-03-19 11:25:47)

fix: move primary ip client request/response structs to schema package
- Move the request and response schema to the schema package
- Rename structs to follow the struct naming conventions:
  - in the schema package we hold `Request` and `Response` structs
  - in the hcloud package we hold `Opts` and `Result` structs
- Fix some response schema being used in the wrong places
- Deprecate the `Result` structs present in the hcloud package (should never be used by users)

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/hetznercloud/hcloud-go/pull/633?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hetznercloud) Report
Attention: Patch coverage is `96.29630%` with `1 line` in your changes missing coverage. Please review.
> Project coverage is 78.93%. Comparing base [(`786f18a`)](https://app.codecov.io/gh/hetznercloud/hcloud-go/commit/786f18a2015b0949530a04e577270ad8b2b5ca9d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hetznercloud) to head [(`cad1a69`)](https://app.codecov.io/gh/hetznercloud/hcloud-go/commit/cad1a69b01c9c21cb38dc23e2ba760f2c43d8aae?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hetznercloud).

| [Files with missing lines](https://app.codecov.io/gh/hetznercloud/hcloud-go/pull/633?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hetznercloud) | Patch % | Lines |
|---|---|---|
| [hcloud/primary\_ip.go](https://app.codecov.io/gh/hetznercloud/hcloud-go/pull/633?src=pr&el=tree&filepath=hcloud%2Fprimary_ip.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hetznercloud#diff-aGNsb3VkL3ByaW1hcnlfaXAuZ28=) | 94.11% | [1 Missing :warning: ](https://app.codecov.io/gh/hetznercloud/hcloud-go/pull/633?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hetznercloud) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #633      +/-   ##
==========================================
+ Coverage   78.71%   78.93%   +0.22%     
==========================================
  Files          50       50              
  Lines        4359     4373      +14     
==========================================
+ Hits         3431     3452      +21     
+ Misses        711      706       -5     
+ Partials      217      215       -2     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/hetznercloud/hcloud-go/pull/633?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hetznercloud).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hetznercloud).

<details><summary>🚀 New features to boost your workflow: </summary>

- ❄ [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Patch:
diff --git a/hcloud/primary_ip.go b/hcloud/primary_ip.go
index e707895..c44e98d 100644
--- a/hcloud/primary_ip.go
+++ b/hcloud/primary_ip.go
@@ -54,7 +54,7 @@ func (p *PrimaryIP) changeDNSPtr(ctx context.Context, client *Client, ip net.IP,
 		DNSPtr: ptr,
 	}
 
-	respBody, resp, err := postRequest[PrimaryIPChangeDNSPtrResult](ctx, client, reqPath, reqBody)
+	respBody, resp, err := postRequest[schema.PrimaryIPActionChangeDNSPtrResponse](ctx, client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
@@ -85,13 +85,13 @@ const (
 // PrimaryIPCreateOpts defines the request to
 // create a Primary IP.
 type PrimaryIPCreateOpts struct {
-	AssigneeID   *int64            `json:"assignee_id,omitempty"`
-	AssigneeType string            `json:"assignee_type"`
-	AutoDelete   *bool             `json:"auto_delete,omitempty"`
-	Datacenter   string            `json:"datacenter,omitempty"`
-	Labels       map[string]string `json:"labels,omitempty"`
-	Name         string            `json:"name"`
-	Type         PrimaryIPType     `json:"type"`
+	AssigneeID   *int64
+	AssigneeType string
+	AutoDelete   *bool
+	Datacenter   string
+	Labels       map[string]string
+	Name         string
+	Type         PrimaryIPType
 }
 
 // PrimaryIPCreateResult defines the response
@@ -104,51 +104,42 @@ type PrimaryIPCreateResult struct {
 // PrimaryIPUpdateOpts defines the request to
 // update a Primary IP.
 type PrimaryIPUpdateOpts struct {
-	AutoDelete *bool              `json:"auto_delete,omitempty"`
-	Labels     *map[string]string `json:"labels,omitempty"`
-	Name       string             `json:"name,omitempty"`
+	AutoDelete *bool
+	Labels     *map[string]string
+	Name       string
 }
 
 // PrimaryIPAssignOpts defines the request to
 // assign a Primary IP to an assignee (usually a server).
 type PrimaryIPAssignOpts struct {
-	ID           int64  `json:"-"`
-	AssigneeID   int64  `json:"assignee_id"`
-	AssigneeType string `json:"assignee_type"`
+	ID           int64
+	AssigneeID   int64
+	AssigneeType string
 }
 
-// PrimaryIPAssignResult defines the response
-// when assigning a Primary IP to a assignee.
-type PrimaryIPAssignResult struct {
-	Action schema.Action `json:"action"`
-}
+// Deprecated: Please use [schema.PrimaryIPActionAssignResponse] instead.
+type PrimaryIPAssignResult = schema.PrimaryIPActionAssignResponse
 
 // PrimaryIPChangeDNSPtrOpts defines the request to
 // change a DNS PTR entry from a Primary IP.
 type PrimaryIPChangeDNSPtrOpts struct {
-	ID     int64  `json:"-"`
-	DNSPtr string `json:"dns_ptr"`
-	IP     string `json:"ip"`
+	ID     int64
+	DNSPtr string
+	IP     string
 }
 
-// PrimaryIPChangeDNSPtrResult defines the response
-// when assigning a Primary IP to a assignee.
-type PrimaryIPChangeDNSPtrResult struct {
-	Action schema.Action `json:"action"`
-}
+// Deprecated: Please use [schema.PrimaryIPChangeDNSPtrResponse] instead.
+type PrimaryIPChangeDNSPtrResult = schema.PrimaryIPActionChangeDNSPtrResponse
 
 // PrimaryIPChangeProtectionOpts defines the request to
 // change protection configuration of a Primary IP.
 type PrimaryIPChangeProtectionOpts struct {
-	ID     int64 `json:"-"`
-	Delete bool  `json:"delete"`
+	ID     int64
+	Delete bool
 }
 
-// PrimaryIPChangeProtectionResult defines the response
-// when changing a protection of a PrimaryIP.
-type PrimaryIPChangeProtectionResult struct {
-	Action schema.Action `json:"action"`
-}
+// Deprecated: Please use [schema.PrimaryIPActionChangeProtectionResponse] instead.
+type PrimaryIPChangeProtectionResult = schema.PrimaryIPActionChangeProtectionResponse
 
 // PrimaryIPClient is a client for the Primary IP API.
 type PrimaryIPClient struct {
@@ -163,7 +154,7 @@ func (c *PrimaryIPClient) GetByID(ctx context.Context, id int64) (*PrimaryIP, *R
 
 	reqPath := fmt.Sprintf(opPath, id)
 
-	respBody, resp, err := getRequest[schema.PrimaryIPGetResult](ctx, c.client, reqPath)
+	respBody, resp, err := getRequest[schema.PrimaryIPGetResponse](ctx, c.client, reqPath)
 	if err != nil {
 		if IsError(err, ErrorCodeNotFound) {
 			return nil, resp, nil
@@ -231,7 +222,7 @@ func (c *PrimaryIPClient) List(ctx context.Context, opts PrimaryIPListOpts) ([]*
 
 	reqPath := fmt.Sprintf(opPath, opts.values().Encode())
 
-	respBody, resp, err := getRequest[schema.PrimaryIPListResult](ctx, c.client, reqPath)
+	respBody, resp, err := getRequest[schema.PrimaryIPListResponse](ctx, c.client, reqPath)
 	if err != nil {
 		return nil, resp, err
 	}
@@ -253,7 +244,7 @@ func (c *PrimaryIPClient) AllWithOpts(ctx context.Context, opts PrimaryIPListOpt
 }
 
 // Create creates a Primary IP.
-func (c *PrimaryIPClient) Create(ctx context.Context, reqBody PrimaryIPCreateOpts) (*PrimaryIPCreateResult, *Response, error) {
+func (c *PrimaryIPClient) Create(ctx context.Context, opts PrimaryIPCreateOpts) (*PrimaryIPCreateResult, *Response, error) {
 	const opPath = "/primary_ips"
 	ctx = ctxutil.SetOpPath(ctx, opPath)
 
@@ -261,6 +252,8 @@ func (c *PrimaryIPClient) Create(ctx context.Context, reqBody PrimaryIPCreateOpt
 
 	reqPath := opPath
 
+	reqBody := SchemaFromPrimaryIPCreateOpts(opts)
+
 	respBody, resp, err := postRequest[schema.PrimaryIPCreateResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return result, resp, err
@@ -285,13 +278,15 @@ func (c *PrimaryIPClient) Delete(ctx context.Context, primaryIP *PrimaryIP) (*Re
 }
 
 // Update updates a Primary IP.
-func (c *PrimaryIPClient) Update(ctx context.Context, primaryIP *PrimaryIP, reqBody PrimaryIPUpdateOpts) (*PrimaryIP, *Response, error) {
+func (c *PrimaryIPClient) Update(ctx context.Context, primaryIP *PrimaryIP, opts PrimaryIPUpdateOpts) (*PrimaryIP, *Response, error) {
 	const opPath = "/primary_ips/%d"
 	ctx = ctxutil.SetOpPath(ctx, opPath)
 
 	reqPath := fmt.Sprintf(opPath, primaryIP.ID)
 
-	respBody, resp, err := putRequest[schema.PrimaryIPUpdateResult](ctx, c.client, reqPath, reqBody)
+	reqBody := SchemaFromPrimaryIPUpdateOpts(opts)
+
+	respBody, resp, err := putRequest[schema.PrimaryIPUpdateResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
@@ -306,9 +301,9 @@ func (c *PrimaryIPClient) Assign(ctx context.Context, opts PrimaryIPAssignOpts)
 
 	reqPath := fmt.Sprintf(opPath, opts.ID)
 
-	reqBody := opts
+	reqBody := SchemaFromPrimaryIPAssignOpts(opts)
 
-	respBody, resp, err := postRequest[PrimaryIPAssignResult](ctx, c.client, reqPath, reqBody)
+	respBody, resp, err := postRequest[schema.PrimaryIPActionAssignResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
@@ -323,7 +318,7 @@ func (c *PrimaryIPClient) Unassign(ctx context.Context, id int64) (*Action, *Res
 
 	reqPath := fmt.Sprintf(opPath, id)
 
-	respBody, resp, err := postRequest[PrimaryIPAssignResult](ctx, c.client, reqPath, nil)
+	respBody, resp, err := postRequest[schema.PrimaryIPActionUnassignResponse](ctx, c.client, reqPath, nil)
 	if err != nil {
 		return nil, resp, err
 	}
@@ -338,9 +333,9 @@ func (c *PrimaryIPClient) ChangeDNSPtr(ctx context.Context, opts PrimaryIPChange
 
 	reqPath := fmt.Sprintf(opPath, opts.ID)
 
-	reqBody := opts
+	reqBody := SchemaFromPrimaryIPChangeDNSPtrOpts(opts)
 
-	respBody, resp, err := postRequest[PrimaryIPChangeDNSPtrResult](ctx, c.client, reqPath, reqBody)
+	respBody, resp, err := postRequest[schema.PrimaryIPActionChangeDNSPtrResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
@@ -355,9 +350,9 @@ func (c *PrimaryIPClient) ChangeProtection(ctx context.Context, opts PrimaryIPCh
 
 	reqPath := fmt.Sprintf(opPath, opts.ID)
 
-	reqBody := opts
+	reqBody := SchemaFromPrimaryIPChangeProtectionOpts(opts)
 
-	respBody, resp, err := postRequest[PrimaryIPChangeProtectionResult](ctx, c.client, reqPath, reqBody)
+	respBody, resp, err := postRequest[schema.PrimaryIPActionChangeProtectionResponse](ctx, c.client, reqPath, reqBody)
 	if err != nil {
 		return nil, resp, err
 	}
diff --git a/hcloud/schema.go b/hcloud/schema.go
index 097b282..df2097f 100644
--- a/hcloud/schema.go
+++ b/hcloud/schema.go
@@ -49,6 +49,26 @@ func SchemaFromPrimaryIP(p *PrimaryIP) schema.PrimaryIP {
 	return c.SchemaFromPrimaryIP(p)
 }
 
+func SchemaFromPrimaryIPCreateOpts(o PrimaryIPCreateOpts) schema.PrimaryIPCreateRequest {
+	return c.SchemaFromPrimaryIPCreateOpts(o)
+}
+
+func SchemaFromPrimaryIPUpdateOpts(o PrimaryIPUpdateOpts) schema.PrimaryIPUpdateRequest {
+	return c.SchemaFromPrimaryIPUpdateOpts(o)
+}
+
+func SchemaFromPrimaryIPChangeDNSPtrOpts(o PrimaryIPChangeDNSPtrOpts) schema.PrimaryIPActionChangeDNSPtrRequest {
+	return c.SchemaFromPrimaryIPChangeDNSPtrOpts(o)
+}
+
+func SchemaFromPrimaryIPChangeProtectionOpts(o PrimaryIPChangeProtectionOpts) schema.PrimaryIPActionChangeProtectionRequest {
+	return c.SchemaFromPrimaryIPChangeProtectionOpts(o)
+}
+
+func SchemaFromPrimaryIPAssignOpts(o PrimaryIPAssignOpts) schema.PrimaryIPActionAssignRequest {
+	return c.SchemaFromPrimaryIPAssignOpts(o)
+}
+
 // ISOFromSchema converts a schema.ISO to an ISO.
 func ISOFromSchema(s schema.ISO) *ISO {
 	return c.ISOFromSchema(s)
diff --git a/hcloud/schema/primary_ip.go b/hcloud/schema/primary_ip.go
index 1901ce8..300711f 100644
--- a/hcloud/schema/primary_ip.go
+++ b/hcloud/schema/primary_ip.go
@@ -31,6 +31,18 @@ type PrimaryIPDNSPTR struct {
 	IP     string `json:"ip"`
 }
 
+// PrimaryIPCreateOpts defines the request to
+// create a Primary IP.
+type PrimaryIPCreateRequest struct {
+	Name         string            `json:"name"`
+	Type         string            `json:"type"`
+	AssigneeType string            `json:"assignee_type"`
+	AssigneeID   *int64            `json:"assignee_id,omitempty"`
+	Labels       map[string]string `json:"labels,omitempty"`
+	AutoDelete   *bool             `json:"auto_delete,omitempty"`
+	Datacenter   string            `json:"datacenter,omitempty"`
+}
+
 // PrimaryIPCreateResponse defines the schema of the response
 // when creating a Primary IP.
 type PrimaryIPCreateResponse struct {
@@ -38,19 +50,27 @@ type PrimaryIPCreateResponse struct {
 	Action    *Action   `json:"action"`
 }
 
-// PrimaryIPGetResult defines the response when retrieving a single Primary IP.
-type PrimaryIPGetResult struct {
+// PrimaryIPGetResponse defines the response when retrieving a single Primary IP.
+type PrimaryIPGetResponse struct {
 	PrimaryIP PrimaryIP `json:"primary_ip"`
 }
 
-// PrimaryIPListResult defines the response when listing Primary IPs.
-type PrimaryIPListResult struct {
+// PrimaryIPListResponse defines the response when listing Primary IPs.
+type PrimaryIPListResponse struct {
 	PrimaryIPs []PrimaryIP `json:"primary_ips"`
 }
 
-// PrimaryIPUpdateResult defines the response
+// PrimaryIPUpdateOpts defines the request to
+// update a Primary IP.
+type PrimaryIPUpdateRequest struct {
+	Name       string            `json:"name,omitempty"`
+	Labels     map[string]string `json:"labels,omitempty"`
+	AutoDelete *bool             `json:"auto_delete,omitempty"`
+}
+
+// PrimaryIPUpdateResponse defines the response
 // when updating a Primary IP.
-type PrimaryIPUpdateResult struct {
+type PrimaryIPUpdateResponse struct {
 	PrimaryIP PrimaryIP `json:"primary_ip"`
 }
 
@@ -60,3 +80,39 @@ type PrimaryIPActionChangeDNSPtrRequest struct {
 	IP     string  `json:"ip"`
 	DNSPtr *string `json:"dns_ptr"`
 }
+
+// PrimaryIPActionChangeDNSPtrResponse defines the response when setting a reverse DNS
+// pointer for a IP address.
+type PrimaryIPActionChangeDNSPtrResponse struct {
+	Action Action `json:"action"`
+}
+
+// PrimaryIPActionAssignRequest defines the request to
+// assign a Primary IP to an assignee (usually a server).
+type PrimaryIPActionAssignRequest struct {
+	AssigneeID   int64  `json:"assignee_id"`
+	AssigneeType string `json:"assignee_type"`
+}
+
+// PrimaryIPActionAssignResponse defines the response when assigning a Primary IP to a
+// assignee.
+type PrimaryIPActionAssignResponse struct {
+	Action Action `json:"action"`
+}
+
+// PrimaryIPActionUnassignResponse defines the response to unassign a Primary IP.
+type PrimaryIPActionUnassignResponse struct {
+	Action Action `json:"action"`
+}
+
+// PrimaryIPActionChangeProtectionRequest defines the request to
+// change protection configuration of a Primary IP.
+type PrimaryIPActionChangeProtectionRequest struct {
+	Delete bool `json:"delete"`
+}
+
+// PrimaryIPActionChangeProtectionResponse defines the response when changing the
+// protection of a Primary IP.
+type PrimaryIPActionChangeProtectionResponse struct {
+	Action Action `json:"action"`
+}
diff --git a/hcloud/schema_gen.go b/hcloud/schema_gen.go
index dabdb64..3b08713 100644
--- a/hcloud/schema_gen.go
+++ b/hcloud/schema_gen.go
@@ -107,6 +107,12 @@ type converter interface {
 	// goverter:map AssigneeID | mapZeroInt64ToNil
 	SchemaFromPrimaryIP(*PrimaryIP) schema.PrimaryIP
 
+	SchemaFromPrimaryIPCreateOpts(PrimaryIPCreateOpts) schema.PrimaryIPCreateRequest
+	SchemaFromPrimaryIPUpdateOpts(PrimaryIPUpdateOpts) schema.PrimaryIPUpdateRequest
+	SchemaFromPrimaryIPChangeDNSPtrOpts(PrimaryIPChangeDNSPtrOpts) schema.PrimaryIPActionChangeDNSPtrRequest
+	SchemaFromPrimaryIPChangeProtectionOpts(PrimaryIPChangeProtectionOpts) schema.PrimaryIPActionChangeProtectionRequest
+	SchemaFromPrimaryIPAssignOpts(PrimaryIPAssignOpts) schema.PrimaryIPActionAssignRequest
+
 	ISOFromSchema(schema.ISO) *ISO
 
 	// We cannot use goverter settings when mapping a struct to a struct pointer
diff --git a/hcloud/zz_primary_ip_client_iface.go b/hcloud/zz_primary_ip_client_iface.go
index 6b8e617..94390d4 100644
--- a/hcloud/zz_primary_ip_client_iface.go
+++ b/hcloud/zz_primary_ip_client_iface.go
@@ -27,11 +27,11 @@ type IPrimaryIPClient interface {
 	// AllWithOpts returns all Primary IPs for the given options.
 	AllWithOpts(ctx context.Context, opts PrimaryIPListOpts) ([]*PrimaryIP, error)
 	// Create creates a Primary IP.
-	Create(ctx context.Context, reqBody PrimaryIPCreateOpts) (*PrimaryIPCreateResult, *Response, error)
+	Create(ctx context.Context, opts PrimaryIPCreateOpts) (*PrimaryIPCreateResult, *Response, error)
 	// Delete deletes a Primary IP.
 	Delete(ctx context.Context, primaryIP *PrimaryIP) (*Response, error)
 	// Update updates a Primary IP.
-	Update(ctx context.Context, primaryIP *PrimaryIP, reqBody PrimaryIPUpdateOpts) (*PrimaryIP, *Response, error)
+	Update(ctx context.Context, primaryIP *PrimaryIP, opts PrimaryIPUpdateOpts) (*PrimaryIP, *Response, error)
 	// Assign a Primary IP to a resource.
 	Assign(ctx context.Context, opts PrimaryIPAssignOpts) (*Action, *Response, error)
 	// Unassign a Primary IP from a resource.
diff --git a/hcloud/zz_schema.go b/hcloud/zz_schema.go
index 1d737e9..25c5893 100644
--- a/hcloud/zz_schema.go
+++ b/hcloud/zz_schema.go
@@ -978,6 +978,44 @@ func (c *converterImpl) SchemaFromPrimaryIP(source *PrimaryIP) schema.PrimaryIP
 	}
 	return schemaPrimaryIP
 }
+func (c *converterImpl) SchemaFromPrimaryIPAssignOpts(source PrimaryIPAssignOpts) schema.PrimaryIPActionAssignRequest {
+	var schemaPrimaryIPActionAssignRequest schema.PrimaryIPActionAssignRequest
+	schemaPrimaryIPActionAssignRequest.AssigneeID = source.AssigneeID
+	schemaPrimaryIPActionAssignRequest.AssigneeType = source.AssigneeType
+	return schemaPrimaryIPActionAssignRequest
+}
+func (c *converterImpl) SchemaFromPrimaryIPChangeDNSPtrOpts(source PrimaryIPChangeDNSPtrOpts) schema.PrimaryIPActionChangeDNSPtrRequest {
+	var schemaPrimaryIPActionChangeDNSPtrRequest schema.PrimaryIPActionChangeDNSPtrRequest
+	schemaPrimaryIPActionChangeDNSPtrRequest.IP = source.IP
+	pString := source.DNSPtr
+	schemaPrimaryIPActionChangeDNSPtrRequest.DNSPtr = &pString
+	return schemaPrimaryIPActionChangeDNSPtrRequest
+}
+func (c *converterImpl) SchemaFromPrimaryIPChangeProtectionOpts(source PrimaryIPChangeProtectionOpts) schema.PrimaryIPActionChangeProtectionRequest {
+	var schemaPrimaryIPActionChangeProtectionRequest schema.PrimaryIPActionChangeProtectionRequest
+	schemaPrimaryIPActionChangeProtectionRequest.Delete = source.Delete
+	return schemaPrimaryIPActionChangeProtectionRequest
+}
+func (c *converterImpl) SchemaFromPrimaryIPCreateOpts(source PrimaryIPCreateOpts) schema.PrimaryIPCreateRequest {
+	var schemaPrimaryIPCreateRequest schema.PrimaryIPCreateRequest
+	schemaPrimaryIPCreateRequest.Name = source.Name
+	schemaPrimaryIPCreateRequest.Type = string(source.Type)
+	schemaPrimaryIPCreateRequest.AssigneeType = source.AssigneeType
+	schemaPrimaryIPCreateRequest.AssigneeID = source.AssigneeID
+	schemaPrimaryIPCreateRequest.Labels = source.Labels
+	schemaPrimaryIPCreateRequest.AutoDelete = source.AutoDelete
+	schemaPrimaryIPCreateRequest.Datacenter = source.Datacenter
+	return schemaPrimaryIPCreateRequest
+}
+func (c *converterImpl) SchemaFromPrimaryIPUpdateOpts(source PrimaryIPUpdateOpts) schema.PrimaryIPUpdateRequest {
+	var schemaPrimaryIPUpdateRequest schema.PrimaryIPUpdateRequest
+	schemaPrimaryIPUpdateRequest.Name = source.Name
+	if source.Labels != nil {
+		schemaPrimaryIPUpdateRequest.Labels = (*source.Labels)
+	}
+	schemaPrimaryIPUpdateRequest.AutoDelete = source.AutoDelete
+	return schemaPrimaryIPUpdateRequest
+}
 func (c *converterImpl) SchemaFromSSHKey(source *SSHKey) schema.SSHKey {
 	var schemaSSHKey schema.SSHKey
 	if source != nil {


Test patch:
diff --git a/hcloud/primary_ip_test.go b/hcloud/primary_ip_test.go
index 97299f6..01d5c9c 100644
--- a/hcloud/primary_ip_test.go
+++ b/hcloud/primary_ip_test.go
@@ -19,7 +19,7 @@ func TestPrimaryIPClient(t *testing.T) {
 		defer env.Teardown()
 
 		env.Mux.HandleFunc("/primary_ips/1", func(w http.ResponseWriter, r *http.Request) {
-			json.NewEncoder(w).Encode(schema.PrimaryIPGetResult{
+			json.NewEncoder(w).Encode(schema.PrimaryIPGetResponse{
 				PrimaryIP: schema.PrimaryIP{
 					ID: 1,
 				},
@@ -84,7 +84,7 @@ func TestPrimaryIPClient(t *testing.T) {
 			if r.URL.RawQuery != "name=fsn1-dc8" {
 				t.Fatal("missing name query")
 			}
-			json.NewEncoder(w).Encode(schema.PrimaryIPListResult{
+			json.NewEncoder(w).Encode(schema.PrimaryIPListResponse{
 				PrimaryIPs: []schema.PrimaryIP{
 					{
 						ID: 1,
@@ -137,7 +137,7 @@ func TestPrimaryIPClient(t *testing.T) {
 			if r.URL.RawQuery != "name=123" {
 				t.Fatal("missing name query")
 			}
-			json.NewEncoder(w).Encode(schema.PrimaryIPListResult{
+			json.NewEncoder(w).Encode(schema.PrimaryIPListResponse{
 				PrimaryIPs: []schema.PrimaryIP{
 					{
 						ID: 1,
@@ -168,7 +168,7 @@ func TestPrimaryIPClient(t *testing.T) {
 			if r.URL.RawQuery != "name=fsn1-dc8" {
 				t.Fatal("missing name query")
 			}
-			json.NewEncoder(w).Encode(schema.PrimaryIPListResult{
+			json.NewEncoder(w).Encode(schema.PrimaryIPListResponse{
 				PrimaryIPs: []schema.PrimaryIP{},
 			})
 		})
@@ -205,7 +205,7 @@ func TestPrimaryIPClient(t *testing.T) {
 			if r.URL.RawQuery != "ip=127.0.0.1" {
 				t.Fatal("missing name query")
 			}
-			json.NewEncoder(w).Encode(schema.PrimaryIPListResult{
+			json.NewEncoder(w).Encode(schema.PrimaryIPListResponse{
 				PrimaryIPs: []schema.PrimaryIP{
 					{
 						ID: 1,
@@ -241,7 +241,7 @@ func TestPrimaryIPClient(t *testing.T) {
 			if name := r.URL.Query().Get("name"); name != "nbg1-dc3" {
 				t.Errorf("expected name nbg1-dc3; got %q", name)
 			}
-			json.NewEncoder(w).Encode(schema.PrimaryIPListResult{
+			json.NewEncoder(w).Encode(schema.PrimaryIPListResponse{
 				PrimaryIPs: []schema.PrimaryIP{
 					{ID: 1},
 					{ID: 2},
@@ -307,20 +307,17 @@ func TestPrimaryIPClient(t *testing.T) {
 		defer env.Teardown()
 
 		env.Mux.HandleFunc("/primary_ips", func(w http.ResponseWriter, r *http.Request) {
-			var reqBody PrimaryIPCreateOpts
+			var reqBody schema.PrimaryIPCreateRequest
 			if r.Method != "POST" {
 				t.Error("expected POST")
 			}
 			w.Header().Set("Content-Type", "application/json")
-			expectedReqBody := PrimaryIPCreateOpts{
+			expectedReqBody := schema.PrimaryIPCreateRequest{
 				Name:         "my-primary-ip",
-				Type:         PrimaryIPTypeIPv4,
+				Type:         "ipv4",
 				AssigneeType: "server",
 				Datacenter:   "fsn-dc14",
-				Labels: func() map[string]string {
-					labels := map[string]string{"key": "value"}
-					return labels
-				}(),
+				Labels:       map[string]string{"key": "value"},
 			}
 			if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil {
 				t.Fatal(err)
@@ -329,9 +326,9 @@ func TestPrimaryIPClient(t *testing.T) {
 				t.Log(cmp.Diff(expectedReqBody, reqBody))
 				t.Error("unexpected request body")
 			}
-			json.NewEncoder(w).Encode(PrimaryIPCreateResult{
-				PrimaryIP: &PrimaryIP{ID: 1},
-				Action:    &Action{ID: 14},
+			json.NewEncoder(w).Encode(schema.PrimaryIPCreateResponse{
+				PrimaryIP: schema.PrimaryIP{ID: 1},
+				Action:    &schema.Action{ID: 14},
 			})
 		})
 
@@ -355,19 +352,16 @@ func TestPrimaryIPClient(t *testing.T) {
 		defer env.Teardown()
 
 		env.Mux.HandleFunc("/primary_ips/1", func(w http.ResponseWriter, r *http.Request) {
-			var reqBody PrimaryIPUpdateOpts
+			var reqBody schema.PrimaryIPUpdateRequest
 			if r.Method != "PUT" {
 				t.Error("expected PUT")
 			}
 			w.Header().Set("Content-Type", "application/json")
 			autoDelete := true
-			expectedReqBody := PrimaryIPUpdateOpts{
+			expectedReqBody := schema.PrimaryIPUpdateRequest{
 				Name:       "my-primary-ip",
 				AutoDelete: &autoDelete,
-				Labels: func() *map[string]string {
-					labels := map[string]string{"key": "value"}
-					return &labels
-				}(),
+				Labels:     map[string]string{"key": "value"},
 			}
 			if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil {
 				t.Fatal(err)
@@ -376,7 +370,7 @@ func TestPrimaryIPClient(t *testing.T) {
 				t.Log(cmp.Diff(expectedReqBody, reqBody))
 				t.Error("unexpected request body")
 			}
-			json.NewEncoder(w).Encode(schema.PrimaryIPUpdateResult{
+			json.NewEncoder(w).Encode(schema.PrimaryIPUpdateResponse{
 				PrimaryIP: schema.PrimaryIP{ID: 1, IP: "2001:db8::/64"},
 			})
 		})
@@ -404,7 +398,7 @@ func TestPrimaryIPClient(t *testing.T) {
 		defer env.Teardown()
 
 		env.Mux.HandleFunc("/primary_ips/1/actions/assign", func(w http.ResponseWriter, r *http.Request) {
-			var reqBody PrimaryIPAssignOpts
+			var reqBody schema.PrimaryIPActionAssignRequest
 			if r.Method != "POST" {
 				t.Error("expected POST")
 			}
@@ -416,9 +410,8 @@ func TestPrimaryIPClient(t *testing.T) {
 
 			assert.Equal(t, int64(1), reqBody.AssigneeID)
 			assert.Equal(t, "server", reqBody.AssigneeType)
-			assert.Equal(t, int64(0), reqBody.ID)
 
-			json.NewEncoder(w).Encode(PrimaryIPAssignResult{
+			json.NewEncoder(w).Encode(schema.PrimaryIPActionAssignResponse{
 				Action: schema.Action{ID: 1},
 			})
 		})
@@ -444,7 +437,7 @@ func TestPrimaryIPClient(t *testing.T) {
 				t.Error("expected POST")
 			}
 			w.Header().Set("Content-Type", "application/json")
-			json.NewEncoder(w).Encode(PrimaryIPAssignResult{
+			json.NewEncoder(w).Encode(schema.PrimaryIPActionUnassignResponse{
 				Action: schema.Action{ID: 1},
 			})
 		})
@@ -461,7 +454,7 @@ func TestPrimaryIPClient(t *testing.T) {
 		defer env.Teardown()
 
 		env.Mux.HandleFunc("/primary_ips/1/actions/change_dns_ptr", func(w http.ResponseWriter, r *http.Request) {
-			var reqBody PrimaryIPChangeDNSPtrOpts
+			var reqBody schema.PrimaryIPActionChangeDNSPtrRequest
 			if r.Method != "POST" {
 				t.Error("expected POST")
 			}
@@ -471,11 +464,10 @@ func TestPrimaryIPClient(t *testing.T) {
 				t.Fatal(err)
 			}
 
-			assert.Equal(t, "value", reqBody.DNSPtr)
+			assert.Equal(t, "value", *reqBody.DNSPtr)
 			assert.Equal(t, "127.0.0.1", reqBody.IP)
-			assert.Equal(t, int64(0), reqBody.ID)
 
-			json.NewEncoder(w).Encode(PrimaryIPChangeDNSPtrResult{
+			json.NewEncoder(w).Encode(schema.PrimaryIPActionChangeDNSPtrResponse{
 				Action: schema.Action{ID: 1},
 			})
 		})
@@ -497,7 +489,7 @@ func TestPrimaryIPClient(t *testing.T) {
 		defer env.Teardown()
 
 		env.Mux.HandleFunc("/primary_ips/1/actions/change_protection", func(w http.ResponseWriter, r *http.Request) {
-			var reqBody PrimaryIPChangeProtectionOpts
+			var reqBody schema.PrimaryIPActionChangeProtectionRequest
 			if r.Method != "POST" {
 				t.Error("expected POST")
 			}
@@ -507,9 +499,8 @@ func TestPrimaryIPClient(t *testing.T) {
 			}
 
 			assert.True(t, reqBody.Delete)
-			assert.Equal(t, int64(0), reqBody.ID)
 
-			json.NewEncoder(w).Encode(PrimaryIPChangeProtectionResult{
+			json.NewEncoder(w).Encode(schema.PrimaryIPActionChangeProtectionResponse{
 				Action: schema.Action{ID: 1},
 			})
 		})