Repository: hetznercloud/hcloud-go
Pull request #4 (2017-12-01 15:49:34)

Add and use schema package which defines the API JSON schema


Patch:
diff --git a/hcloud/action.go b/hcloud/action.go
index 1f8a6a9..edd4142 100644
--- a/hcloud/action.go
+++ b/hcloud/action.go
@@ -2,9 +2,10 @@ package hcloud
 
 import (
 	"context"
-	"encoding/json"
 	"fmt"
 	"time"
+
+	"github.com/hetznercloud/hcloud-go/hcloud/schema"
 )
 
 // Action represents an action in the Hetzner Cloud.
@@ -19,35 +20,21 @@ type Action struct {
 	ErrorMessage string
 }
 
-// UnmarshalJSON implements json.Unmarshaler.
-func (a *Action) UnmarshalJSON(data []byte) error {
-	var v struct {
-		ID       int       `json:"id"`
-		Status   string    `json:"status"`
-		Command  string    `json:"command"`
-		Progress int       `json:"progress"`
-		Started  time.Time `json:"started"`
-		Finished time.Time `json:"finished"`
-		Error    struct {
-			Code    string `json:"code"`
-			Message string `json:"message"`
-		} `json:"error"`
+// ActionFromSchema converts a schema.Action to an Action.
+func ActionFromSchema(s schema.Action) Action {
+	action := Action{
+		ID:       s.ID,
+		Status:   s.Status,
+		Command:  s.Command,
+		Progress: s.Progress,
+		Started:  s.Started,
+		Finished: s.Finished,
 	}
-
-	if err := json.Unmarshal(data, &v); err != nil {
-		return err
+	if s.Error != nil {
+		action.ErrorCode = s.Error.Code
+		action.ErrorMessage = s.Error.Message
 	}
-
-	a.ID = v.ID
-	a.Status = v.Status
-	a.Command = v.Command
-	a.Progress = v.Progress
-	a.Started = v.Started
-	a.Finished = v.Finished
-	a.ErrorCode = v.Error.Code
-	a.ErrorMessage = v.Error.Message
-
-	return nil
+	return action
 }
 
 // ActionClient is a client for the actions API.
@@ -63,11 +50,12 @@ func (c *ActionClient) Get(ctx context.Context, id int) (*Action, *Response, err
 	}
 
 	var body struct {
-		Action *Action `json:"action"`
+		Action schema.Action `json:"action"`
 	}
 	resp, err := c.client.Do(req, &body)
 	if err != nil {
 		return nil, nil, err
 	}
-	return body.Action, resp, nil
+	action := ActionFromSchema(body.Action)
+	return &action, resp, nil
 }
diff --git a/hcloud/iso.go b/hcloud/iso.go
index 6c0e4f9..d0ce904 100644
--- a/hcloud/iso.go
+++ b/hcloud/iso.go
@@ -1,8 +1,6 @@
 package hcloud
 
-import (
-	"encoding/json"
-)
+import "github.com/hetznercloud/hcloud-go/hcloud/schema"
 
 // ISO represents an ISO image in the Hetzner Cloud.
 type ISO struct {
@@ -23,23 +21,12 @@ const (
 	ISOTypePrivate = "private"
 )
 
-// UnmarshalJSON implements json.Unmarshaler.
-func (i *ISO) UnmarshalJSON(data []byte) error {
-	var v struct {
-		ID          int     `json:"id"`
-		Name        string  `json:"name"`
-		Description string  `json:"description"`
-		Type        ISOType `json:"type"`
-	}
-
-	if err := json.Unmarshal(data, &v); err != nil {
-		return err
+// ISOFromSchema converts a schema.ISO to an ISO.
+func ISOFromSchema(s schema.ISO) ISO {
+	return ISO{
+		ID:          s.ID,
+		Name:        s.Name,
+		Description: s.Description,
+		Type:        ISOType(s.Type),
 	}
-
-	i.ID = v.ID
-	i.Name = v.Name
-	i.Description = v.Description
-	i.Type = v.Type
-
-	return nil
 }
diff --git a/hcloud/schema/action.go b/hcloud/schema/action.go
new file mode 100644
index 0000000..3503809
--- /dev/null
+++ b/hcloud/schema/action.go
@@ -0,0 +1,21 @@
+package schema
+
+import "time"
+
+// Action defines the schema of an action.
+type Action struct {
+	ID       int          `json:"id"`
+	Status   string       `json:"status"`
+	Command  string       `json:"command"`
+	Progress int          `json:"progress"`
+	Started  time.Time    `json:"started"`
+	Finished time.Time    `json:"finished"`
+	Error    *ActionError `json:"error"`
+}
+
+// ActionError defines the schema of an error embedded
+// in an action.
+type ActionError struct {
+	Code    string `json:"code"`
+	Message string `json:"message"`
+}
diff --git a/hcloud/schema/iso.go b/hcloud/schema/iso.go
new file mode 100644
index 0000000..e217dcd
--- /dev/null
+++ b/hcloud/schema/iso.go
@@ -0,0 +1,9 @@
+package schema
+
+// ISO defines the schema of an ISO image.
+type ISO struct {
+	ID          int    `json:"id"`
+	Name        string `json:"name"`
+	Description string `json:"description"`
+	Type        string `json:"type"`
+}
diff --git a/hcloud/schema/server.go b/hcloud/schema/server.go
new file mode 100644
index 0000000..e51e115
--- /dev/null
+++ b/hcloud/schema/server.go
@@ -0,0 +1,50 @@
+package schema
+
+import "time"
+
+// Server defines the schema of a server.
+type Server struct {
+	ID              int             `json:"id"`
+	Name            string          `json:"name"`
+	Status          string          `json:"status"`
+	Created         time.Time       `json:"created"`
+	PublicNet       ServerPublicNet `json:"public_net"`
+	ServerType      ServerType      `json:"server_type"`
+	IncludedTraffic uint64          `json:"included_traffic"`
+	OutgoingTraffic uint64          `json:"outgoing_traffic"`
+	IngoingTraffic  uint64          `json:"ingoing_traffic"`
+	BackupWindow    string          `json:"backup_window"`
+	RescueEnabled   bool            `json:"rescue_enabled"`
+	ISO             *ISO            `json:"iso"`
+}
+
+// ServerPublicNet defines the schema of a server's
+// public network information.
+type ServerPublicNet struct {
+	IPv4        ServerPublicNetIPv4 `json:"ipv4"`
+	IPv6        ServerPublicNetIPv6 `json:"ipv6"`
+	FloatingIPs []int               `json:"floating_ips"`
+}
+
+// ServerPublicNetIPv4 defines the schema of a server's public
+// network information for an IPv4.
+type ServerPublicNetIPv4 struct {
+	IP      string `json:"ip"`
+	Blocked bool   `json:"blocked"`
+	DNSPtr  string `json:"dns_ptr"`
+}
+
+// ServerPublicNetIPv6 defines the schema of a server's public
+// network information for an IPv6.
+type ServerPublicNetIPv6 struct {
+	IP      string                      `json:"ip"`
+	Blocked bool                        `json:"blocked"`
+	DNSPtr  []ServerPublicNetIPv6DNSPtr `json:"dns_ptr"`
+}
+
+// ServerPublicNetIPv6DNSPtr defines the schema of a server's
+// public network information for an IPv6 reverse DNS.
+type ServerPublicNetIPv6DNSPtr struct {
+	IP     string `json:"ip"`
+	DNSPtr string `json:"dns_ptr"`
+}
diff --git a/hcloud/schema/server_type.go b/hcloud/schema/server_type.go
new file mode 100644
index 0000000..9a2b703
--- /dev/null
+++ b/hcloud/schema/server_type.go
@@ -0,0 +1,12 @@
+package schema
+
+// ServerType defines the schema of a server type.
+type ServerType struct {
+	ID          int     `json:"id"`
+	Name        string  `json:"name"`
+	Description string  `json:"description"`
+	Cores       int     `json:"cores"`
+	Memory      float32 `json:"memory"`
+	Disk        int     `json:"disk"`
+	StorageType string  `json:"storage_type"`
+}
diff --git a/hcloud/schema/ssh_key.go b/hcloud/schema/ssh_key.go
new file mode 100644
index 0000000..75e10d2
--- /dev/null
+++ b/hcloud/schema/ssh_key.go
@@ -0,0 +1,9 @@
+package schema
+
+// SSHKey defines the schema of a SSH key.
+type SSHKey struct {
+	ID          int    `json:"id"`
+	Name        string `json:"name"`
+	Fingerprint string `json:"fingerprint"`
+	PublicKey   string `json:"public_key"`
+}
diff --git a/hcloud/server.go b/hcloud/server.go
index 6dcc702..dde646c 100644
--- a/hcloud/server.go
+++ b/hcloud/server.go
@@ -7,6 +7,8 @@ import (
 	"errors"
 	"fmt"
 	"time"
+
+	"github.com/hetznercloud/hcloud-go/hcloud/schema"
 )
 
 // Server represents a server in the Hetzner Cloud.
@@ -25,41 +27,26 @@ type Server struct {
 	ISO             *ISO
 }
 
-// UnmarshalJSON implements json.Unmarshaler.
-func (s *Server) UnmarshalJSON(data []byte) error {
-	var v struct {
-		ID              int             `json:"id"`
-		Name            string          `json:"name"`
-		Status          string          `json:"status"`
-		Created         time.Time       `json:"created"`
-		PublicNet       ServerPublicNet `json:"public_net"`
-		ServerType      ServerType      `json:"server_type"`
-		IncludedTraffic uint64          `json:"included_traffic"`
-		OutgoingTraffic uint64          `json:"outgoing_traffic"`
-		IngoingTraffic  uint64          `json:"ingoing_traffic"`
-		BackupWindow    string          `json:"backup_window"`
-		RescueEnabled   bool            `json:"rescue_enabled"`
-		ISO             *ISO            `json:"iso"`
+// ServerFromSchema converts a schema.Server to a Server.
+func ServerFromSchema(s schema.Server) Server {
+	server := Server{
+		ID:              s.ID,
+		Name:            s.Name,
+		Status:          ServerStatus(s.Status),
+		Created:         s.Created,
+		PublicNet:       ServerPublicNetFromSchema(s.PublicNet),
+		ServerType:      ServerTypeFromSchema(s.ServerType),
+		IncludedTraffic: s.IncludedTraffic,
+		OutgoingTraffic: s.OutgoingTraffic,
+		IngoingTraffic:  s.IngoingTraffic,
+		BackupWindow:    s.BackupWindow,
+		RescueEnabled:   s.RescueEnabled,
 	}
-
-	if err := json.Unmarshal(data, &v); err != nil {
-		return err
+	if s.ISO != nil {
+		iso := ISOFromSchema(*s.ISO)
+		server.ISO = &iso
 	}
-
-	s.ID = v.ID
-	s.Name = v.Name
-	s.Status = ServerStatus(v.Status)
-	s.Created = v.Created
-	s.PublicNet = v.PublicNet
-	s.ServerType = v.ServerType
-	s.IncludedTraffic = v.IncludedTraffic
-	s.OutgoingTraffic = v.OutgoingTraffic
-	s.IngoingTraffic = v.IngoingTraffic
-	s.BackupWindow = v.BackupWindow
-	s.RescueEnabled = v.RescueEnabled
-	s.ISO = v.ISO
-
-	return nil
+	return server
 }
 
 // ServerStatus specifies a server's status.
@@ -83,26 +70,16 @@ type ServerPublicNet struct {
 	FloatingIPs []*FloatingIP
 }
 
-// UnmarshalJSON implements json.Unmarshaler.
-func (s *ServerPublicNet) UnmarshalJSON(data []byte) error {
-	var v struct {
-		IPv4        ServerPublicNetIPv4 `json:"ipv4"`
-		IPv6        ServerPublicNetIPv6 `json:"ipv6"`
-		FloatingIPs []int               `json:"floating_ips"`
-	}
-
-	if err := json.Unmarshal(data, &v); err != nil {
-		return err
+// ServerPublicNetFromSchema converts a schema.ServerPublicNet to a ServerPublicNet.
+func ServerPublicNetFromSchema(s schema.ServerPublicNet) ServerPublicNet {
+	publicNet := ServerPublicNet{
+		IPv4: ServerPublicNetIPv4FromSchema(s.IPv4),
+		IPv6: ServerPublicNetIPv6FromSchema(s.IPv6),
 	}
-
-	s.IPv4 = v.IPv4
-	s.IPv6 = v.IPv6
-
-	for _, f := range v.FloatingIPs {
-		s.FloatingIPs = append(s.FloatingIPs, &FloatingIP{ID: f})
+	for _, id := range s.FloatingIPs {
+		publicNet.FloatingIPs = append(publicNet.FloatingIPs, &FloatingIP{ID: id})
 	}
-
-	return nil
+	return publicNet
 }
 
 // ServerPublicNetIPv4 represents a server's public IPv4 network.
@@ -112,23 +89,14 @@ type ServerPublicNetIPv4 struct {
 	DNSPtr  string
 }
 
-// UnmarshalJSON implements json.Unmarshaler.
-func (s *ServerPublicNetIPv4) UnmarshalJSON(data []byte) error {
-	var v struct {
-		IP      string `json:"ip"`
-		Blocked bool   `json:"blocked"`
-		DNSPtr  string `json:"dns_ptr"`
+// ServerPublicNetIPv4FromSchema converts a schema.ServerPublicNetIPv4 to
+// a ServerPublicNetIPv4.
+func ServerPublicNetIPv4FromSchema(s schema.ServerPublicNetIPv4) ServerPublicNetIPv4 {
+	return ServerPublicNetIPv4{
+		IP:      s.IP,
+		Blocked: s.Blocked,
+		DNSPtr:  s.DNSPtr,
 	}
-
-	if err := json.Unmarshal(data, &v); err != nil {
-		return err
-	}
-
-	s.IP = v.IP
-	s.Blocked = v.Blocked
-	s.DNSPtr = v.DNSPtr
-
-	return nil
 }
 
 // ServerPublicNetIPv6 represents a server's public IPv6 network.
@@ -138,23 +106,17 @@ type ServerPublicNetIPv6 struct {
 	DNSPtr  []ServerPublicNetIPv6DNSPtr
 }
 
-// UnmarshalJSON implements json.Unmarshaler.
-func (s *ServerPublicNetIPv6) UnmarshalJSON(data []byte) error {
-	var v struct {
-		IP      string                      `json:"ip"`
-		Blocked bool                        `json:"blocked"`
-		DNSPtr  []ServerPublicNetIPv6DNSPtr `json:"dns_ptr"`
+// ServerPublicNetIPv6FromSchema converts a schema.ServerPublicNetIPv6 to
+// a ServerPublicNetIPv6.
+func ServerPublicNetIPv6FromSchema(s schema.ServerPublicNetIPv6) ServerPublicNetIPv6 {
+	ipv6 := ServerPublicNetIPv6{
+		IP:      s.IP,
+		Blocked: s.Blocked,
 	}
-
-	if err := json.Unmarshal(data, &v); err != nil {
-		return err
+	for _, dnsPtr := range s.DNSPtr {
+		ipv6.DNSPtr = append(ipv6.DNSPtr, ServerPublicNetIPv6DNSPtrFromSchema(dnsPtr))
 	}
-
-	s.IP = v.IP
-	s.Blocked = v.Blocked
-	s.DNSPtr = v.DNSPtr
-
-	return nil
+	return ipv6
 }
 
 // ServerPublicNetIPv6DNSPtr represents a server's public IPv6 reverse DNS.
@@ -163,21 +125,13 @@ type ServerPublicNetIPv6DNSPtr struct {
 	DNSPtr string
 }
 
-// UnmarshalJSON implements json.Unmarshaler.
-func (s *ServerPublicNetIPv6DNSPtr) UnmarshalJSON(data []byte) error {
-	var v struct {
-		IP     string `json:"ip"`
-		DNSPtr string `json:"dns_ptr"`
-	}
-
-	if err := json.Unmarshal(data, &v); err != nil {
-		return err
+// ServerPublicNetIPv6DNSPtrFromSchema converts a schema.ServerPublicNetIPv6DNSPtr
+// to a ServerPublicNetIPv6DNSPtr.
+func ServerPublicNetIPv6DNSPtrFromSchema(s schema.ServerPublicNetIPv6DNSPtr) ServerPublicNetIPv6DNSPtr {
+	return ServerPublicNetIPv6DNSPtr{
+		IP:     s.IP,
+		DNSPtr: s.DNSPtr,
 	}
-
-	s.IP = v.IP
-	s.DNSPtr = v.DNSPtr
-
-	return nil
 }
 
 // ServerClient is a client for the servers API.
@@ -193,13 +147,14 @@ func (c *ServerClient) Get(ctx context.Context, id int) (*Server, *Response, err
 	}
 
 	var body struct {
-		Server *Server `json:"server"`
+		Server schema.Server `json:"server"`
 	}
 	resp, err := c.client.Do(req, &body)
 	if err != nil {
 		return nil, nil, err
 	}
-	return body.Server, resp, nil
+	server := ServerFromSchema(body.Server)
+	return &server, resp, nil
 }
 
 // ServerListOpts specifies options for listing servers.
@@ -216,13 +171,18 @@ func (c *ServerClient) List(ctx context.Context, opts ServerListOpts) ([]*Server
 	}
 
 	var body struct {
-		Servers []*Server `json:"servers"`
+		Servers []schema.Server `json:"servers"`
 	}
 	resp, err := c.client.Do(req, &body)
 	if err != nil {
 		return nil, nil, err
 	}
-	return body.Servers, resp, nil
+	servers := make([]*Server, 0, len(body.Servers))
+	for _, s := range body.Servers {
+		server := ServerFromSchema(s)
+		servers = append(servers, &server)
+	}
+	return servers, resp, nil
 }
 
 // All returns all servers.
@@ -307,17 +267,22 @@ func (c *ServerClient) Create(ctx context.Context, opts ServerCreateOpts) (Serve
 		return ServerCreateResult{}, nil, err
 	}
 
-	var respBody struct {
-		Server *Server `json:"server"`
-		Action *Action `json:"action"`
-	}
+	var (
+		respBody struct {
+			Server schema.Server  `json:"server"`
+			Action *schema.Action `json:"action"`
+		}
+		result ServerCreateResult
+	)
 	resp, err := c.client.Do(req, &respBody)
 	if err != nil {
 		return ServerCreateResult{}, resp, err
 	}
-	result := ServerCreateResult{
-		Server: respBody.Server,
-		Action: respBody.Action,
+	server := ServerFromSchema(respBody.Server)
+	result.Server = &server
+	if respBody.Action != nil {
+		action := ActionFromSchema(*respBody.Action)
+		result.Action = &action
 	}
 	return result, resp, nil
 }
diff --git a/hcloud/server_type.go b/hcloud/server_type.go
index 46fe74b..7af7787 100644
--- a/hcloud/server_type.go
+++ b/hcloud/server_type.go
@@ -1,6 +1,6 @@
 package hcloud
 
-import "encoding/json"
+import "github.com/hetznercloud/hcloud-go/hcloud/schema"
 
 // ServerType represents a server type in the Hetzner Cloud.
 type ServerType struct {
@@ -13,31 +13,17 @@ type ServerType struct {
 	StorageType StorageType
 }
 
-// UnmarshalJSON implements json.Unmarshaler.
-func (s *ServerType) UnmarshalJSON(data []byte) error {
-	var serverType struct {
-		ID          int     `json:"id"`
-		Name        string  `json:"name"`
-		Description string  `json:"description"`
-		Cores       int     `json:"cores"`
-		Memory      float32 `json:"memory"`
-		Disk        int     `json:"disk"`
-		StorageType string  `json:"storage_type"`
+// ServerTypeFromSchema converts a schema.ServerType to a ServerType.
+func ServerTypeFromSchema(s schema.ServerType) ServerType {
+	return ServerType{
+		ID:          s.ID,
+		Name:        s.Name,
+		Description: s.Description,
+		Cores:       s.Cores,
+		Memory:      s.Memory,
+		Disk:        s.Disk,
+		StorageType: StorageType(s.StorageType),
 	}
-
-	if err := json.Unmarshal(data, &serverType); err != nil {
-		return err
-	}
-
-	s.ID = serverType.ID
-	s.Name = serverType.Name
-	s.Description = serverType.Description
-	s.Cores = serverType.Cores
-	s.Memory = serverType.Memory
-	s.Disk = serverType.Disk
-	s.StorageType = StorageType(serverType.StorageType)
-
-	return nil
 }
 
 // StorageType specifies the type of storage.
diff --git a/hcloud/ssh_key.go b/hcloud/ssh_key.go
index f0dbeb9..93d74cc 100644
--- a/hcloud/ssh_key.go
+++ b/hcloud/ssh_key.go
@@ -6,6 +6,8 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+
+	"github.com/hetznercloud/hcloud-go/hcloud/schema"
 )
 
 // SSHKey represents a SSH key in the Hetzner Cloud.
@@ -16,25 +18,14 @@ type SSHKey struct {
 	PublicKey   string
 }
 
-// UnmarshalJSON implements json.Unmarshaler.
-func (s *SSHKey) UnmarshalJSON(data []byte) error {
-	var v struct {
-		ID          int    `json:"id"`
-		Name        string `json:"name"`
-		Fingerprint string `json:"fingerprint"`
-		PublicKey   string `json:"public_key"`
-	}
-
-	if err := json.Unmarshal(data, &v); err != nil {
-		return err
+// SSHKeyFromSchema converts a schema.SSHKey to a SSHKey.
+func SSHKeyFromSchema(s schema.SSHKey) SSHKey {
+	return SSHKey{
+		ID:          s.ID,
+		Name:        s.Name,
+		Fingerprint: s.Fingerprint,
+		PublicKey:   s.PublicKey,
 	}
-
-	s.ID = v.ID
-	s.Name = v.Name
-	s.Fingerprint = v.Fingerprint
-	s.PublicKey = v.PublicKey
-
-	return nil
 }
 
 // SSHKeyClient is a client for the SSH keys API.
@@ -50,13 +41,14 @@ func (c *SSHKeyClient) Get(ctx context.Context, id int) (*SSHKey, *Response, err
 	}
 
 	var body struct {
-		SSHKey *SSHKey `json:"ssh_key"`
+		SSHKey schema.SSHKey `json:"ssh_key"`
 	}
 	resp, err := c.client.Do(req, &body)
 	if err != nil {
 		return nil, nil, err
 	}
-	return body.SSHKey, resp, nil
+	sshKey := SSHKeyFromSchema(body.SSHKey)
+	return &sshKey, resp, nil
 }
 
 // SSHKeyListOpts specifies options for listing SSH keys.
@@ -73,13 +65,18 @@ func (c *SSHKeyClient) List(ctx context.Context, opts SSHKeyListOpts) ([]*SSHKey
 	}
 
 	var body struct {
-		SSHKeys []*SSHKey `json:"ssh_keys"`
+		SSHKeys []schema.SSHKey `json:"ssh_keys"`
 	}
 	resp, err := c.client.Do(req, &body)
 	if err != nil {
 		return nil, nil, err
 	}
-	return body.SSHKeys, resp, nil
+	sshKeys := make([]*SSHKey, 0, len(body.SSHKeys))
+	for _, s := range body.SSHKeys {
+		sshKey := SSHKeyFromSchema(s)
+		sshKeys = append(sshKeys, &sshKey)
+	}
+	return sshKeys, resp, nil
 }
 
 // All returns all SSH keys.
@@ -146,13 +143,14 @@ func (c *SSHKeyClient) Create(ctx context.Context, opts SSHKeyCreateOpts) (*SSHK
 	}
 
 	var respBody struct {
-		SSHKey *SSHKey `json:"ssh_key"`
+		SSHKey schema.SSHKey `json:"ssh_key"`
 	}
 	resp, err := c.client.Do(req, &respBody)
 	if err != nil {
 		return nil, resp, err
 	}
-	return respBody.SSHKey, resp, nil
+	sshKey := SSHKeyFromSchema(respBody.SSHKey)
+	return &sshKey, resp, nil
 }
 
 // Delete deletes a SSH key.


Test patch:
diff --git a/hcloud/action_test.go b/hcloud/action_test.go
index 752ff75..5af4b0e 100644
--- a/hcloud/action_test.go
+++ b/hcloud/action_test.go
@@ -4,9 +4,11 @@ import (
 	"encoding/json"
 	"testing"
 	"time"
+
+	"github.com/hetznercloud/hcloud-go/hcloud/schema"
 )
 
-func TestActionUnmarshalJSON(t *testing.T) {
+func TestActionFromSchema(t *testing.T) {
 	data := []byte(`{
 		"id": 1,
 		"command": "create_server",
@@ -26,33 +28,34 @@ func TestActionUnmarshalJSON(t *testing.T) {
 		}
 	}`)
 
-	var v Action
-	if err := json.Unmarshal(data, &v); err != nil {
+	var s schema.Action
+	if err := json.Unmarshal(data, &s); err != nil {
 		t.Fatal(err)
 	}
+	action := ActionFromSchema(s)
 
-	if v.ID != 1 {
-		t.Errorf("unexpected ID: %v", v.ID)
+	if action.ID != 1 {
+		t.Errorf("unexpected ID: %v", action.ID)
 	}
-	if v.Command != "create_server" {
-		t.Errorf("unexpected command: %v", v.Command)
+	if action.Command != "create_server" {
+		t.Errorf("unexpected command: %v", action.Command)
 	}
-	if v.Status != "success" {
-		t.Errorf("unexpected status: %v", v.Status)
+	if action.Status != "success" {
+		t.Errorf("unexpected status: %v", action.Status)
 	}
-	if v.Progress != 100 {
-		t.Errorf("unexpected progress: %d", v.Progress)
+	if action.Progress != 100 {
+		t.Errorf("unexpected progress: %d", action.Progress)
 	}
-	if !v.Started.Equal(time.Date(2016, 1, 30, 23, 55, 0, 0, time.UTC)) {
-		t.Errorf("unexpected started: %v", v.Started)
+	if !action.Started.Equal(time.Date(2016, 1, 30, 23, 55, 0, 0, time.UTC)) {
+		t.Errorf("unexpected started: %v", action.Started)
 	}
-	if !v.Finished.Equal(time.Date(2016, 1, 30, 23, 56, 13, 0, time.UTC)) {
-		t.Errorf("unexpected finished: %v", v.Started)
+	if !action.Finished.Equal(time.Date(2016, 1, 30, 23, 56, 13, 0, time.UTC)) {
+		t.Errorf("unexpected finished: %v", action.Started)
 	}
-	if v.ErrorCode != "action_failed" {
-		t.Errorf("unexpected error code: %v", v.ErrorCode)
+	if action.ErrorCode != "action_failed" {
+		t.Errorf("unexpected error code: %v", action.ErrorCode)
 	}
-	if v.ErrorMessage != "Action failed" {
-		t.Errorf("unexpected error message: %v", v.ErrorMessage)
+	if action.ErrorMessage != "Action failed" {
+		t.Errorf("unexpected error message: %v", action.ErrorMessage)
 	}
 }
diff --git a/hcloud/server_test.go b/hcloud/server_test.go
index fe7d8d8..eaa34db 100644
--- a/hcloud/server_test.go
+++ b/hcloud/server_test.go
@@ -7,9 +7,11 @@ import (
 	"net/http"
 	"testing"
 	"time"
+
+	"github.com/hetznercloud/hcloud-go/hcloud/schema"
 )
 
-func TestServerUnmarshalJSON(t *testing.T) {
+func TestServerFromSchema(t *testing.T) {
 	data := []byte(`{
 		"id": 1,
 		"name": "server.example.com",
@@ -48,69 +50,71 @@ func TestServerUnmarshalJSON(t *testing.T) {
 		}
 	}`)
 
-	var v Server
-	if err := json.Unmarshal(data, &v); err != nil {
+	var s schema.Server
+	if err := json.Unmarshal(data, &s); err != nil {
 		t.Fatal(err)
 	}
+	server := ServerFromSchema(s)
 
-	if v.ID != 1 {
-		t.Errorf("unexpected ID: %v", v.ID)
+	if server.ID != 1 {
+		t.Errorf("unexpected ID: %v", server.ID)
 	}
-	if v.Name != "server.example.com" {
-		t.Errorf("unexpected name: %v", v.Name)
+	if server.Name != "server.example.com" {
+		t.Errorf("unexpected name: %v", server.Name)
 	}
-	if v.Status != ServerStatusRunning {
-		t.Errorf("unexpected status: %v", v.Status)
+	if server.Status != ServerStatusRunning {
+		t.Errorf("unexpected status: %v", server.Status)
 	}
-	if !v.Created.Equal(time.Date(2017, 8, 16, 17, 29, 14, 0, time.UTC)) {
-		t.Errorf("unexpected created date: %v", v.Created)
+	if !server.Created.Equal(time.Date(2017, 8, 16, 17, 29, 14, 0, time.UTC)) {
+		t.Errorf("unexpected created date: %v", server.Created)
 	}
-	if v.PublicNet.IPv4.IP != "1.2.3.4" {
-		t.Errorf("unexpected public net IPv4 IP: %v", v.PublicNet.IPv4.IP)
+	if server.PublicNet.IPv4.IP != "1.2.3.4" {
+		t.Errorf("unexpected public net IPv4 IP: %v", server.PublicNet.IPv4.IP)
 	}
-	if v.ServerType.ID != 2 {
-		t.Errorf("unexpected server type ID: %v", v.ServerType.ID)
+	if server.ServerType.ID != 2 {
+		t.Errorf("unexpected server type ID: %v", server.ServerType.ID)
 	}
-	if v.IncludedTraffic != 654321 {
-		t.Errorf("unexpected included traffic: %v", v.IncludedTraffic)
+	if server.IncludedTraffic != 654321 {
+		t.Errorf("unexpected included traffic: %v", server.IncludedTraffic)
 	}
-	if v.OutgoingTraffic != 123456 {
-		t.Errorf("unexpected outgoing traffic: %v", v.OutgoingTraffic)
+	if server.OutgoingTraffic != 123456 {
+		t.Errorf("unexpected outgoing traffic: %v", server.OutgoingTraffic)
 	}
-	if v.IngoingTraffic != 7891011 {
-		t.Errorf("unexpected ingoing traffic: %v", v.IngoingTraffic)
+	if server.IngoingTraffic != 7891011 {
+		t.Errorf("unexpected ingoing traffic: %v", server.IngoingTraffic)
 	}
-	if v.BackupWindow != "22-02" {
-		t.Errorf("unexpected backup window: %v", v.BackupWindow)
+	if server.BackupWindow != "22-02" {
+		t.Errorf("unexpected backup window: %v", server.BackupWindow)
 	}
-	if !v.RescueEnabled {
-		t.Errorf("unexpected rescue enabled state: %v", v.RescueEnabled)
+	if !server.RescueEnabled {
+		t.Errorf("unexpected rescue enabled state: %v", server.RescueEnabled)
 	}
-	if v.ISO == nil || v.ISO.ID != 4711 {
-		t.Errorf("unexpected ISO: %v", v.ISO)
+	if server.ISO == nil || server.ISO.ID != 4711 {
+		t.Errorf("unexpected ISO: %v", server.ISO)
 	}
 }
 
-func TestServerUnmarshalJSONNoTraffic(t *testing.T) {
+func TestServerFromSchemaNoTraffic(t *testing.T) {
 	data := []byte(`{
 		"outgoing_traffic": null,
 		"ingoing_traffic": null
 	}`)
 
-	var v Server
-	if err := json.Unmarshal(data, &v); err != nil {
+	var s schema.Server
+	if err := json.Unmarshal(data, &s); err != nil {
 		t.Fatal(err)
 	}
+	server := ServerFromSchema(s)
 
-	if v.OutgoingTraffic != 0 {
-		t.Errorf("unexpected outgoing traffic: %v", v.OutgoingTraffic)
+	if server.OutgoingTraffic != 0 {
+		t.Errorf("unexpected outgoing traffic: %v", server.OutgoingTraffic)
 	}
-	if v.IngoingTraffic != 0 {
-		t.Errorf("unexpected ingoing traffic: %v", v.IngoingTraffic)
+	if server.IngoingTraffic != 0 {
+		t.Errorf("unexpected ingoing traffic: %v", server.IngoingTraffic)
 	}
 }
 
-func TestServerPublicNetUnmarshalJSON(t *testing.T) {
+func TestServerPublicNetFromSchema(t *testing.T) {
 	data := []byte(`{
 		"ipv4": {
 			"ip": "1.2.3.4",
@@ -125,46 +129,48 @@ func TestServerPublicNetUnmarshalJSON(t *testing.T) {
       		"floating_ips": [4]
 	}`)
 
-	var v ServerPublicNet
-	if err := json.Unmarshal(data, &v); err != nil {
+	var s schema.ServerPublicNet
+	if err := json.Unmarshal(data, &s); err != nil {
 		t.Fatal(err)
 	}
+	publicNet := ServerPublicNetFromSchema(s)
 
-	if v.IPv4.IP != "1.2.3.4" {
-		t.Errorf("unexpected IPv4 IP: %v", v.IPv4.IP)
+	if publicNet.IPv4.IP != "1.2.3.4" {
+		t.Errorf("unexpected IPv4 IP: %v", publicNet.IPv4.IP)
 	}
-	if v.IPv6.IP != "2a01:4f8:1c19:1403::/64" {
-		t.Errorf("unexpected IPv6 IP: %v", v.IPv6.IP)
+	if publicNet.IPv6.IP != "2a01:4f8:1c19:1403::/64" {
+		t.Errorf("unexpected IPv6 IP: %v", publicNet.IPv6.IP)
 	}
-	if len(v.FloatingIPs) != 1 || v.FloatingIPs[0].ID != 4 {
-		t.Errorf("unexpected Floating IPs: %v", v.FloatingIPs)
+	if len(publicNet.FloatingIPs) != 1 || publicNet.FloatingIPs[0].ID != 4 {
+		t.Errorf("unexpected Floating IPs: %v", publicNet.FloatingIPs)
 	}
 }
 
-func TestServerPublicNetIPv4UnmarshalJSON(t *testing.T) {
+func TestServerPublicNetIPv4FromSchema(t *testing.T) {
 	data := []byte(`{
 		"ip": "1.2.3.4",
 		"blocked": true,
 		"dns_ptr": "server.example.com"
 	}`)
 
-	var v ServerPublicNetIPv4
-	if err := json.Unmarshal(data, &v); err != nil {
+	var s schema.ServerPublicNetIPv4
+	if err := json.Unmarshal(data, &s); err != nil {
 		t.Fatal(err)
 	}
+	ipv4 := ServerPublicNetIPv4FromSchema(s)
 
-	if v.IP != "1.2.3.4" {
-		t.Errorf("unexpected IP: %v", v.IP)
+	if ipv4.IP != "1.2.3.4" {
+		t.Errorf("unexpected IP: %v", ipv4.IP)
 	}
-	if !v.Blocked {
-		t.Errorf("unexpected blocked state: %v", v.Blocked)
+	if !ipv4.Blocked {
+		t.Errorf("unexpected blocked state: %v", ipv4.Blocked)
 	}
-	if v.DNSPtr != "server.example.com" {
-		t.Errorf("unexpected DNS ptr: %v", v.DNSPtr)
+	if ipv4.DNSPtr != "server.example.com" {
+		t.Errorf("unexpected DNS ptr: %v", ipv4.DNSPtr)
 	}
 }
 
-func TestServerPublicNetIPv6UnmarshalJSON(t *testing.T) {
+func TestServerPublicNetIPv6FromSchema(t *testing.T) {
 	data := []byte(`{
 		"ip": "2a01:4f8:1c11:3400::/64",
 		"blocked": true,
@@ -176,38 +182,40 @@ func TestServerPublicNetIPv6UnmarshalJSON(t *testing.T) {
 		]
 	}`)
 
-	var v ServerPublicNetIPv6
-	if err := json.Unmarshal(data, &v); err != nil {
+	var s schema.ServerPublicNetIPv6
+	if err := json.Unmarshal(data, &s); err != nil {
 		t.Fatal(err)
 	}
+	ipv6 := ServerPublicNetIPv6FromSchema(s)
 
-	if v.IP != "2a01:4f8:1c11:3400::/64" {
-		t.Errorf("unexpected IP: %v", v.IP)
+	if ipv6.IP != "2a01:4f8:1c11:3400::/64" {
+		t.Errorf("unexpected IP: %v", ipv6.IP)
 	}
-	if !v.Blocked {
-		t.Errorf("unexpected blocked state: %v", v.Blocked)
+	if !ipv6.Blocked {
+		t.Errorf("unexpected blocked state: %v", ipv6.Blocked)
 	}
-	if len(v.DNSPtr) != 1 {
-		t.Errorf("unexpected DNS ptr: %v", v.DNSPtr)
+	if len(ipv6.DNSPtr) != 1 {
+		t.Errorf("unexpected DNS ptr: %v", ipv6.DNSPtr)
 	}
 }
 
-func TestServerPublicNetIPv6DNSPtrUnmarshalJSON(t *testing.T) {
+func TestServerPublicNetIPv6DNSPtrFromSchema(t *testing.T) {
 	data := []byte(`{
 		"ip": "2a01:4f8:1c11:3400::1/64",
 		"dns_ptr": "server01.example.com"
 	}`)
 
-	var v ServerPublicNetIPv6DNSPtr
-	if err := json.Unmarshal(data, &v); err != nil {
+	var s schema.ServerPublicNetIPv6DNSPtr
+	if err := json.Unmarshal(data, &s); err != nil {
 		t.Fatal(err)
 	}
+	dnsPtr := ServerPublicNetIPv6DNSPtrFromSchema(s)
 
-	if v.IP != "2a01:4f8:1c11:3400::1/64" {
-		t.Errorf("unexpected IP: %v", v.IP)
+	if dnsPtr.IP != "2a01:4f8:1c11:3400::1/64" {
+		t.Errorf("unexpected IP: %v", dnsPtr.IP)
 	}
-	if v.DNSPtr != "server01.example.com" {
-		t.Errorf("unexpected DNS ptr: %v", v.DNSPtr)
+	if dnsPtr.DNSPtr != "server01.example.com" {
+		t.Errorf("unexpected DNS ptr: %v", dnsPtr.DNSPtr)
 	}
 }
 
diff --git a/hcloud/server_type_test.go b/hcloud/server_type_test.go
index 331f245..6a0b9a7 100644
--- a/hcloud/server_type_test.go
+++ b/hcloud/server_type_test.go
@@ -3,9 +3,11 @@ package hcloud
 import (
 	"encoding/json"
 	"testing"
+
+	"github.com/hetznercloud/hcloud-go/hcloud/schema"
 )
 
-func TestServerTypeUnmarshalJSON(t *testing.T) {
+func TestServerTypeFromSchema(t *testing.T) {
 	data := []byte(`{
 		"id": 1,
 		"name": "cx10",
@@ -16,10 +18,11 @@ func TestServerTypeUnmarshalJSON(t *testing.T) {
 		"storage_type": "local"
 	}`)
 
-	var serverType ServerType
-	if err := json.Unmarshal(data, &serverType); err != nil {
+	var s schema.ServerType
+	if err := json.Unmarshal(data, &s); err != nil {
 		t.Fatal(err)
 	}
+	serverType := ServerTypeFromSchema(s)
 
 	if serverType.ID != 1 {
 		t.Errorf("unexpected ID: %v", serverType.ID)
diff --git a/hcloud/ssh_key_test.go b/hcloud/ssh_key_test.go
index 2af4d1a..81b89a7 100644
--- a/hcloud/ssh_key_test.go
+++ b/hcloud/ssh_key_test.go
@@ -6,9 +6,11 @@ import (
 	"fmt"
 	"net/http"
 	"testing"
+
+	"github.com/hetznercloud/hcloud-go/hcloud/schema"
 )
 
-func TestSSHKeyUnmarshalJSON(t *testing.T) {
+func TestSSHKeyFromSchema(t *testing.T) {
 	data := []byte(`{
 		"id": 2323,
 		"name": "My key",
@@ -16,22 +18,23 @@ func TestSSHKeyUnmarshalJSON(t *testing.T) {
 		"public_key": "ssh-rsa AAAjjk76kgf...Xt"
 	}`)
 
-	var v SSHKey
-	if err := json.Unmarshal(data, &v); err != nil {
+	var s schema.SSHKey
+	if err := json.Unmarshal(data, &s); err != nil {
 		t.Fatal(err)
 	}
+	sshKey := SSHKeyFromSchema(s)
 
-	if v.ID != 2323 {
-		t.Errorf("unexpected ID: %v", v.ID)
+	if sshKey.ID != 2323 {
+		t.Errorf("unexpected ID: %v", sshKey.ID)
 	}
-	if v.Name != "My key" {
-		t.Errorf("unexpected name: %v", v.Name)
+	if sshKey.Name != "My key" {
+		t.Errorf("unexpected name: %v", sshKey.Name)
 	}
-	if v.Fingerprint != "b7:2f:30:a0:2f:6c:58:6c:21:04:58:61:ba:06:3b:2c" {
-		t.Errorf("unexpected fingerprint: %v", v.Fingerprint)
+	if sshKey.Fingerprint != "b7:2f:30:a0:2f:6c:58:6c:21:04:58:61:ba:06:3b:2c" {
+		t.Errorf("unexpected fingerprint: %v", sshKey.Fingerprint)
 	}
-	if v.PublicKey != "ssh-rsa AAAjjk76kgf...Xt" {
-		t.Errorf("unexpected public key: %v", v.PublicKey)
+	if sshKey.PublicKey != "ssh-rsa AAAjjk76kgf...Xt" {
+		t.Errorf("unexpected public key: %v", sshKey.PublicKey)
 	}
 }