package rest import ( "encoding/json" "fmt" "time" ) // Error is used to unpack every error returned by the api type Error struct { // Message contains the error from the api as a string Message string `json:"error"` // StatusCode contains a HTTP status code that the api server responded with StatusCode int } func (e *Error) Error() string { return e.Message } // Response will contain a body (which can be empty), status code and the Method. // This struct will be used to decode a response from the api server. type Response struct { Body []byte StatusCode int Method Method ContentLocation string } // Time is defined because the transip api server does not return a rfc 3339 time string // and golang requires this. So we need to do manual time parsing, by defining our own time struct // encapsulating time.Time. type Time struct { // Time item containing the actual parsed time object time.Time } // Date is defined because the transip api server returns date strings, not parsed by golang by default. // So we need to do manual time parsing, by defining our own date struct encapsulating time.Time. type Date struct { // Time item containing the actual parsed time object time.Time } // UnmarshalJSON parses datetime strings returned by the transip api func (tt *Time) UnmarshalJSON(input []byte) error { loc, err := time.LoadLocation("Europe/Amsterdam") if err != nil { return err } // don't parse on empty dates if string(input) == `""` { return nil } newTime, err := time.ParseInLocation(`"2006-01-02 15:04:05"`, string(input), loc) if err != nil { return err } tt.Time = newTime return nil } // UnmarshalJSON parses date strings returned by the transip api func (td *Date) UnmarshalJSON(input []byte) error { loc, err := time.LoadLocation("Europe/Amsterdam") if err != nil { return err } // don't parse on empty dates if string(input) == `""` { return nil } newTime, err := time.ParseInLocation(`"2006-01-02"`, string(input), loc) if err != nil { return err } td.Time = newTime return nil } // ParseResponse will convert a Response struct to the given interface. // When the rest response has no body it will return without filling the dest variable. func (r *Response) ParseResponse(dest interface{}) error { // do response error checking if !r.Method.StatusCodeOK(r.StatusCode) { return r.parseErrorResponse() } if len(r.Body) == 0 { return nil } return json.Unmarshal(r.Body, dest) } // parseErrorResponse tries to unmarshal the error response body // so we can return it to the user func (r *Response) parseErrorResponse() error { // there is no response content so we also don't need to parse it if len(r.Body) == 0 { return &Error{ Message: fmt.Sprintf("error response without body from api server status code '%d'", r.StatusCode), StatusCode: r.StatusCode, } } var errorResponse Error err := json.Unmarshal(r.Body, &errorResponse) if err != nil { return &Error{ Message: fmt.Sprintf("response error could not be decoded '%s'", string(r.Body)), StatusCode: r.StatusCode, } } // set the exposed status code so users can check on this errorResponse.StatusCode = r.StatusCode return &errorResponse }