You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

143 lines
3.2 KiB

  1. package dynamodb
  2. import simplejson "github.com/bitly/go-simplejson"
  3. import (
  4. "errors"
  5. "github.com/goamz/goamz/aws"
  6. "io/ioutil"
  7. "log"
  8. "net/http"
  9. "strings"
  10. "time"
  11. )
  12. type Server struct {
  13. Auth aws.Auth
  14. Region aws.Region
  15. }
  16. /*
  17. type Query struct {
  18. Query string
  19. }
  20. */
  21. /*
  22. func NewQuery(queryParts []string) *Query {
  23. return &Query{
  24. "{" + strings.Join(queryParts, ",") + "}",
  25. }
  26. }
  27. */
  28. const (
  29. // DynamoDBAPIPrefix is the versioned prefix for DynamoDB API commands.
  30. DynamoDBAPIPrefix = "DynamoDB_20120810."
  31. // DynamoDBStreamsAPIPrefix is the versioned prefix for DynamoDB Streams API commands.
  32. DynamoDBStreamsAPIPrefix = "DynamoDBStreams_20120810."
  33. )
  34. // Specific error constants
  35. var ErrNotFound = errors.New("Item not found")
  36. // Error represents an error in an operation with Dynamodb (following goamz/s3)
  37. type Error struct {
  38. StatusCode int // HTTP status code (200, 403, ...)
  39. Status string
  40. Code string // Dynamodb error code ("MalformedQueryString", ...)
  41. Message string // The human-oriented error message
  42. }
  43. func (e *Error) Error() string {
  44. return e.Code + ": " + e.Message
  45. }
  46. func buildError(r *http.Response, jsonBody []byte) error {
  47. ddbError := Error{
  48. StatusCode: r.StatusCode,
  49. Status: r.Status,
  50. }
  51. // TODO return error if Unmarshal fails?
  52. json, err := simplejson.NewJson(jsonBody)
  53. if err != nil {
  54. log.Printf("Failed to parse body as JSON")
  55. return err
  56. }
  57. ddbError.Message = json.Get("message").MustString()
  58. // Of the form: com.amazon.coral.validate#ValidationException
  59. // We only want the last part
  60. codeStr := json.Get("__type").MustString()
  61. hashIndex := strings.Index(codeStr, "#")
  62. if hashIndex > 0 {
  63. codeStr = codeStr[hashIndex+1:]
  64. }
  65. ddbError.Code = codeStr
  66. return &ddbError
  67. }
  68. func (s *Server) queryServer(target string, query *Query) ([]byte, error) {
  69. data := strings.NewReader(query.String())
  70. var endpoint string
  71. if isStreamsTarget(target) {
  72. endpoint = s.Region.DynamoDBStreamsEndpoint
  73. } else {
  74. endpoint = s.Region.DynamoDBEndpoint
  75. }
  76. hreq, err := http.NewRequest("POST", endpoint+"/", data)
  77. if err != nil {
  78. return nil, err
  79. }
  80. hreq.Header.Set("Content-Type", "application/x-amz-json-1.0")
  81. hreq.Header.Set("X-Amz-Date", time.Now().UTC().Format(aws.ISO8601BasicFormat))
  82. hreq.Header.Set("X-Amz-Target", target)
  83. token := s.Auth.Token()
  84. if token != "" {
  85. hreq.Header.Set("X-Amz-Security-Token", token)
  86. }
  87. signer := aws.NewV4Signer(s.Auth, "dynamodb", s.Region)
  88. signer.Sign(hreq)
  89. resp, err := http.DefaultClient.Do(hreq)
  90. if err != nil {
  91. log.Printf("Error calling Amazon")
  92. return nil, err
  93. }
  94. defer resp.Body.Close()
  95. body, err := ioutil.ReadAll(resp.Body)
  96. if err != nil {
  97. log.Printf("Could not read response body")
  98. return nil, err
  99. }
  100. // http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ErrorHandling.html
  101. // "A response code of 200 indicates the operation was successful."
  102. if resp.StatusCode != 200 {
  103. ddbErr := buildError(resp, body)
  104. return nil, ddbErr
  105. }
  106. return body, nil
  107. }
  108. func target(name string) string {
  109. return DynamoDBAPIPrefix + name
  110. }
  111. func streamsTarget(name string) string {
  112. return DynamoDBStreamsAPIPrefix + name
  113. }
  114. func isStreamsTarget(target string) bool {
  115. return strings.HasPrefix(target, DynamoDBStreamsAPIPrefix)
  116. }