package redriver import ( "encoding/base64" "errors" "fmt" "net/url" "strconv" "strings" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/aws/arn" "github.com/aws/aws-sdk-go-v2/service/sqs/types" ) func ARNFromURL(qURL string) (arn.ARN, error) { a := arn.ARN{} u, err := url.Parse(qURL) if err != nil { return a, fmt.Errorf("queue URL %q is not a valid URL: %w", qURL, err) } path := u.EscapedPath() pathParts := strings.Split(strings.Trim(path, "/"), "/") if len(pathParts) != 2 { return a, fmt.Errorf("queue path %q does not have exactly 2 parts", path) } hostParts := strings.Split(u.Host, ".") if len(hostParts) != 4 { // ..amazonaws.com return a, fmt.Errorf("queue host %q does not have exactly 4 parts", u.Host) } a = arn.ARN{ Partition: "aws", Service: hostParts[0], Region: hostParts[1], AccountID: pathParts[0], Resource: pathParts[1], } return a, nil } func NameFromURL(qURL string) (name string, err error) { u, err := url.Parse(qURL) if err != nil { return "", fmt.Errorf("queue URL %q is not a valid URL: %w", qURL, err) } path := u.EscapedPath() parts := strings.Split(strings.Trim(path, "/"), "/") if len(parts) != 2 { return "", fmt.Errorf("queue path %q does not have exactly 2 parts", path) } return parts[1], nil } func NameFromARN(qARN arn.ARN) string { return qARN.Resource } func NameFromARNString(qARN string) (name string, err error) { a, err := arn.Parse(qARN) if err != nil { return "", fmt.Errorf("queue \"ARN\" %q is not an valid ARN", qARN) } return NameFromARN(a), nil } func URLFromARNString(qARN string) (name string, err error) { a, err := arn.Parse(qARN) if err != nil { return "", fmt.Errorf("queue \"ARN\" %q is not an valid ARN", qARN) } return URLFromARN(a) } func URLFromARN(qARN arn.ARN) (name string, err error) { path, err := url.JoinPath("/", qARN.AccountID, qARN.Resource) if err != nil { return "", fmt.Errorf("incorrect queue ARN: %w", err) } u := url.URL{ Scheme: "https", Host: fmt.Sprintf("%s.%s.%s", qARN.Service, qARN.Region, "amazonaws.com"), Path: path, } return u.String(), nil } // MessageAttributeValuesFromJSONable attempts to convert a plain JSON to Message.MessageAttributes field value. // // FIXME we should not be storing those maps in JSONable form anyway, because that loses information. func MessageAttributeValuesFromJSONable(in map[string]any) (map[string]types.MessageAttributeValue, error) { m := make(map[string]types.MessageAttributeValue, len(in)) for k, v := range in { mav := types.MessageAttributeValue{StringValue: aws.String(fmt.Sprint(v))} switch v.(type) { case int: mav.DataType = aws.String("Number") case string: mav.DataType = aws.String("String") default: return nil, fmt.Errorf("message attributes contain a non-convertible value: %T", v) } m[k] = mav } return m, nil } func JSONableFromMessageAttributeValues(m map[string]types.MessageAttributeValue) (map[string]any, error) { j := make(map[string]any) for name, attr := range m { if attr.DataType == nil { return nil, errors.New("empty DataType on message attribute value") } switch t := *attr.DataType; t { case "String": if attr.StringValue == nil { return nil, fmt.Errorf("nil string value on string field %q", name) } j[name] = *attr.StringValue case "Number": if attr.StringValue == nil { return nil, fmt.Errorf("nil string value on number field %q", name) } n, err := strconv.Atoi(*attr.StringValue) if err != nil { return nil, fmt.Errorf("invalid numeric string on number field %q", name) } j[name] = n case "Binary": if attr.BinaryValue == nil { j[name] = nil continue } in := attr.BinaryValue out := make([]byte, base64.StdEncoding.DecodedLen(len(in))) _, err := base64.StdEncoding.Decode(out, in) if err != nil { return nil, fmt.Errorf("failed decoding binary field %q: %w", name, err) } j[name] = out default: return nil, fmt.Errorf("unimplemented DataType %q", t) } } return j, nil }