Skip to content

Commit 3566963

Browse files
committed
useragent: add FromSlice function
This function accepts a slice of any value and converts to a slice of `UserAgentProduct` structs. This functionality will be used by upstream clients which accept a list of raw string content to be appended to the User-Agent header, rather than a structured object with name, version, and comment sections explicitly defined.
1 parent 3038d6c commit 3566963

File tree

2 files changed

+165
-0
lines changed

2 files changed

+165
-0
lines changed

useragent/from.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package useragent
5+
6+
import (
7+
"strings"
8+
9+
"github.com/hashicorp/aws-sdk-go-base/v2/internal/config"
10+
"github.com/hashicorp/aws-sdk-go-base/v2/internal/slices"
11+
)
12+
13+
// FromSlice applies the conversion defined in [fromString] to all elements
14+
// of a slice
15+
//
16+
// Slices of types which cannot assert to a string, empty string values, and string
17+
// values which do not match the expected `{product}/{version} ({comment})`
18+
// pattern (where version and comment are optional) return a zero value struct.
19+
func FromSlice[T any](sl []T) config.UserAgentProducts {
20+
return slices.ApplyToAll(sl, func(v T) config.UserAgentProduct {
21+
if s, ok := any(v).(string); ok && s != "" {
22+
return fromString(s)
23+
}
24+
return config.UserAgentProduct{}
25+
})
26+
}
27+
28+
// fromString separates the provided string into the constituent parts
29+
// expected by the UserAgentProduct struct
30+
//
31+
// Values which do not match the expected `{product}/{version} ({comment})`
32+
// pattern, where version and comment are optional, return a zero value struct.
33+
func fromString(s string) config.UserAgentProduct {
34+
parts := strings.Split(s, "/")
35+
switch len(parts) {
36+
case 1:
37+
return config.UserAgentProduct{Name: parts[0]}
38+
case 2:
39+
subparts := strings.Split(parts[1], "(")
40+
if len(subparts) == 2 {
41+
version := strings.TrimSpace(subparts[0])
42+
comment := strings.TrimSuffix(subparts[1], ")")
43+
return config.UserAgentProduct{Name: parts[0], Version: version, Comment: comment}
44+
}
45+
return config.UserAgentProduct{Name: parts[0], Version: parts[1]}
46+
}
47+
48+
return config.UserAgentProduct{}
49+
}

useragent/from_test.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package useragent
5+
6+
import (
7+
"reflect"
8+
"testing"
9+
10+
"github.com/hashicorp/aws-sdk-go-base/v2/internal/config"
11+
)
12+
13+
func TestFromSlice(t *testing.T) {
14+
t.Parallel()
15+
16+
tests := []struct {
17+
name string
18+
s []any
19+
want config.UserAgentProducts
20+
}{
21+
{
22+
"nil",
23+
nil,
24+
config.UserAgentProducts{},
25+
},
26+
{
27+
"non-string element",
28+
[]any{false},
29+
config.UserAgentProducts{config.UserAgentProduct{}},
30+
},
31+
{
32+
"valid string",
33+
[]any{"my-product/v1.2.3"},
34+
config.UserAgentProducts{
35+
config.UserAgentProduct{
36+
Name: "my-product",
37+
Version: "v1.2.3",
38+
},
39+
},
40+
},
41+
{
42+
"valid and invalid string",
43+
[]any{"my-product/v1.2.3", "foo/bar/baz/qux"},
44+
config.UserAgentProducts{
45+
config.UserAgentProduct{
46+
Name: "my-product",
47+
Version: "v1.2.3",
48+
},
49+
config.UserAgentProduct{},
50+
},
51+
},
52+
}
53+
for _, tt := range tests {
54+
t.Run(tt.name, func(t *testing.T) {
55+
t.Parallel()
56+
got := FromSlice(tt.s)
57+
if !reflect.DeepEqual(got, tt.want) {
58+
t.Errorf("FromSlice() = %+v, want %+v", got, tt.want)
59+
}
60+
})
61+
}
62+
}
63+
64+
func Test_fromString(t *testing.T) {
65+
t.Parallel()
66+
67+
tests := []struct {
68+
name string
69+
s string
70+
want config.UserAgentProduct
71+
}{
72+
{
73+
"empty",
74+
"",
75+
config.UserAgentProduct{},
76+
},
77+
{
78+
"name only",
79+
"my-product",
80+
config.UserAgentProduct{
81+
Name: "my-product",
82+
},
83+
},
84+
{
85+
"name and version",
86+
"my-product/v1.2.3",
87+
config.UserAgentProduct{
88+
Name: "my-product",
89+
Version: "v1.2.3",
90+
},
91+
},
92+
{
93+
"name, version, and comment",
94+
"my-product/v1.2.3 (a comment)",
95+
config.UserAgentProduct{
96+
Name: "my-product",
97+
Version: "v1.2.3",
98+
Comment: "a comment",
99+
},
100+
},
101+
{
102+
"all the slash",
103+
"foo/bar/baz/qux",
104+
config.UserAgentProduct{},
105+
},
106+
}
107+
for _, tt := range tests {
108+
t.Run(tt.name, func(t *testing.T) {
109+
t.Parallel()
110+
got := fromString(tt.s)
111+
if !reflect.DeepEqual(got, tt.want) {
112+
t.Errorf("fromString() = %+v, want %+v", got, tt.want)
113+
}
114+
})
115+
}
116+
}

0 commit comments

Comments
 (0)