converters.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. package redriver
  2. import (
  3. "encoding/base64"
  4. "errors"
  5. "fmt"
  6. "net/url"
  7. "strconv"
  8. "strings"
  9. "github.com/aws/aws-sdk-go-v2/aws"
  10. "github.com/aws/aws-sdk-go-v2/aws/arn"
  11. "github.com/aws/aws-sdk-go-v2/service/sqs/types"
  12. )
  13. func ARNFromURL(qURL string) (arn.ARN, error) {
  14. a := arn.ARN{}
  15. u, err := url.Parse(qURL)
  16. if err != nil {
  17. return a, fmt.Errorf("queue URL %q is not a valid URL: %w", qURL, err)
  18. }
  19. path := u.EscapedPath()
  20. pathParts := strings.Split(strings.Trim(path, "/"), "/")
  21. if len(pathParts) != 2 {
  22. return a, fmt.Errorf("queue path %q does not have exactly 2 parts", path)
  23. }
  24. hostParts := strings.Split(u.Host, ".")
  25. if len(hostParts) != 4 { // <service>.<region>.amazonaws.com
  26. return a, fmt.Errorf("queue host %q does not have exactly 4 parts", u.Host)
  27. }
  28. a = arn.ARN{
  29. Partition: "aws",
  30. Service: hostParts[0],
  31. Region: hostParts[1],
  32. AccountID: pathParts[0],
  33. Resource: pathParts[1],
  34. }
  35. return a, nil
  36. }
  37. func NameFromURL(qURL string) (name string, err error) {
  38. u, err := url.Parse(qURL)
  39. if err != nil {
  40. return "", fmt.Errorf("queue URL %q is not a valid URL: %w", qURL, err)
  41. }
  42. path := u.EscapedPath()
  43. parts := strings.Split(strings.Trim(path, "/"), "/")
  44. if len(parts) != 2 {
  45. return "", fmt.Errorf("queue path %q does not have exactly 2 parts", path)
  46. }
  47. return parts[1], nil
  48. }
  49. func NameFromARN(qARN arn.ARN) string {
  50. return qARN.Resource
  51. }
  52. func NameFromARNString(qARN string) (name string, err error) {
  53. a, err := arn.Parse(qARN)
  54. if err != nil {
  55. return "", fmt.Errorf("queue \"ARN\" %q is not an valid ARN", qARN)
  56. }
  57. return NameFromARN(a), nil
  58. }
  59. func URLFromARNString(qARN string) (name string, err error) {
  60. a, err := arn.Parse(qARN)
  61. if err != nil {
  62. return "", fmt.Errorf("queue \"ARN\" %q is not an valid ARN", qARN)
  63. }
  64. return URLFromARN(a)
  65. }
  66. func URLFromARN(qARN arn.ARN) (name string, err error) {
  67. path, err := url.JoinPath("/", qARN.AccountID, qARN.Resource)
  68. if err != nil {
  69. return "", fmt.Errorf("incorrect queue ARN: %w", err)
  70. }
  71. u := url.URL{
  72. Scheme: "https",
  73. Host: fmt.Sprintf("%s.%s.%s", qARN.Service, qARN.Region, "amazonaws.com"),
  74. Path: path,
  75. }
  76. return u.String(), nil
  77. }
  78. // MessageAttributeValuesFromJSONable attempts to convert a plain JSON to Message.MessageAttributes field value.
  79. //
  80. // FIXME we should not be storing those maps in JSONable form anyway, because that loses information.
  81. func MessageAttributeValuesFromJSONable(in map[string]any) (map[string]types.MessageAttributeValue, error) {
  82. m := make(map[string]types.MessageAttributeValue, len(in))
  83. for k, v := range in {
  84. mav := types.MessageAttributeValue{StringValue: aws.String(fmt.Sprint(v))}
  85. switch v.(type) {
  86. case int:
  87. mav.DataType = aws.String("Number")
  88. case string:
  89. mav.DataType = aws.String("String")
  90. default:
  91. return nil, fmt.Errorf("message attributes contain a non-convertible value: %T", v)
  92. }
  93. m[k] = mav
  94. }
  95. return m, nil
  96. }
  97. func JSONableFromMessageAttributeValues(m map[string]types.MessageAttributeValue) (map[string]any, error) {
  98. j := make(map[string]any)
  99. for name, attr := range m {
  100. if attr.DataType == nil {
  101. return nil, errors.New("empty DataType on message attribute value")
  102. }
  103. switch t := *attr.DataType; t {
  104. case "String":
  105. if attr.StringValue == nil {
  106. return nil, fmt.Errorf("nil string value on string field %q", name)
  107. }
  108. j[name] = *attr.StringValue
  109. case "Number":
  110. if attr.StringValue == nil {
  111. return nil, fmt.Errorf("nil string value on number field %q", name)
  112. }
  113. n, err := strconv.Atoi(*attr.StringValue)
  114. if err != nil {
  115. return nil, fmt.Errorf("invalid numeric string on number field %q", name)
  116. }
  117. j[name] = n
  118. case "Binary":
  119. if attr.BinaryValue == nil {
  120. j[name] = nil
  121. continue
  122. }
  123. in := attr.BinaryValue
  124. out := make([]byte, base64.StdEncoding.DecodedLen(len(in)))
  125. _, err := base64.StdEncoding.Decode(out, in)
  126. if err != nil {
  127. return nil, fmt.Errorf("failed decoding binary field %q: %w", name, err)
  128. }
  129. j[name] = out
  130. default:
  131. return nil, fmt.Errorf("unimplemented DataType %q", t)
  132. }
  133. }
  134. return j, nil
  135. }