response.go raw

   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