1 package rest
2 3 import (
4 "encoding/json"
5 "fmt"
6 "time"
7 )
8 9 // Error is used to unpack every error returned by the api
10 type Error struct {
11 // Message contains the error from the api as a string
12 Message string `json:"error"`
13 // StatusCode contains a HTTP status code that the api server responded with
14 StatusCode int
15 }
16 17 func (e *Error) Error() string {
18 return e.Message
19 }
20 21 // Response will contain a body (which can be empty), status code and the Method.
22 // This struct will be used to decode a response from the api server.
23 type Response struct {
24 Body []byte
25 StatusCode int
26 Method Method
27 ContentLocation string
28 }
29 30 // Time is defined because the transip api server does not return a rfc 3339 time string
31 // and golang requires this. So we need to do manual time parsing, by defining our own time struct
32 // encapsulating time.Time.
33 type Time struct {
34 // Time item containing the actual parsed time object
35 time.Time
36 }
37 38 // Date is defined because the transip api server returns date strings, not parsed by golang by default.
39 // So we need to do manual time parsing, by defining our own date struct encapsulating time.Time.
40 type Date struct {
41 // Time item containing the actual parsed time object
42 time.Time
43 }
44 45 // UnmarshalJSON parses datetime strings returned by the transip api
46 func (tt *Time) UnmarshalJSON(input []byte) error {
47 loc, err := time.LoadLocation("Europe/Amsterdam")
48 if err != nil {
49 return err
50 }
51 // don't parse on empty dates
52 if string(input) == `""` {
53 return nil
54 }
55 newTime, err := time.ParseInLocation(`"2006-01-02 15:04:05"`, string(input), loc)
56 if err != nil {
57 return err
58 }
59 60 tt.Time = newTime
61 return nil
62 }
63 64 // UnmarshalJSON parses date strings returned by the transip api
65 func (td *Date) UnmarshalJSON(input []byte) error {
66 loc, err := time.LoadLocation("Europe/Amsterdam")
67 if err != nil {
68 return err
69 }
70 // don't parse on empty dates
71 if string(input) == `""` {
72 return nil
73 }
74 newTime, err := time.ParseInLocation(`"2006-01-02"`, string(input), loc)
75 if err != nil {
76 return err
77 }
78 79 td.Time = newTime
80 return nil
81 }
82 83 // ParseResponse will convert a Response struct to the given interface.
84 // When the rest response has no body it will return without filling the dest variable.
85 func (r *Response) ParseResponse(dest interface{}) error {
86 // do response error checking
87 if !r.Method.StatusCodeOK(r.StatusCode) {
88 return r.parseErrorResponse()
89 }
90 91 if len(r.Body) == 0 {
92 return nil
93 }
94 95 return json.Unmarshal(r.Body, dest)
96 }
97 98 // parseErrorResponse tries to unmarshal the error response body
99 // so we can return it to the user
100 func (r *Response) parseErrorResponse() error {
101 // there is no response content so we also don't need to parse it
102 if len(r.Body) == 0 {
103 return &Error{
104 Message: fmt.Sprintf("error response without body from api server status code '%d'", r.StatusCode),
105 StatusCode: r.StatusCode,
106 }
107 }
108 109 var errorResponse Error
110 err := json.Unmarshal(r.Body, &errorResponse)
111 if err != nil {
112 return &Error{
113 Message: fmt.Sprintf("response error could not be decoded '%s'", string(r.Body)),
114 StatusCode: r.StatusCode,
115 }
116 }
117 118 // set the exposed status code so users can check on this
119 errorResponse.StatusCode = r.StatusCode
120 121 return &errorResponse
122 }
123