Skip to content

Commit 94a8289

Browse files
authored
Merge pull request #276 from xmidt-org/denopink/feat/options-integration
feat: add options pattern
2 parents 24d29af + 1dc9f50 commit 94a8289

15 files changed

+619
-361
lines changed

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ report.json
1717
# Ignore the vendor structure
1818
*vendor/
1919

20+
# VSCode
21+
*.code-workspace
22+
.vscode/*
23+
.dev/*
24+
2025
# Ignore the various build artifacts
2126
.ignore
2227

anclafx/provide.go

-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ func Provide() fx.Option {
1919
ancla.ProvideListener,
2020
ancla.ProvideDefaultListenerWatchers,
2121
chrysom.ProvideBasicClient,
22-
chrysom.ProvideDefaultListenerReader,
2322
chrysom.ProvideListenerClient,
2423
),
2524
chrysom.ProvideMetrics(),

anclafx/provide_test.go

+14-17
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
package anclafx_test
44

55
import (
6-
"context"
76
"testing"
87

98
"github.com/stretchr/testify/require"
@@ -19,10 +18,9 @@ import (
1918
type out struct {
2019
fx.Out
2120

22-
Factory *touchstone.Factory
23-
BasicClientConfig chrysom.BasicClientConfig
24-
GetLogger chrysom.GetLogger
25-
SetLogger chrysom.SetLogger
21+
Factory *touchstone.Factory
22+
ClientOptions chrysom.ClientOptions `group:"client_options,flatten"`
23+
ListenerOptions chrysom.ListenerOptions `group:"listener_options,flatten"`
2624
}
2725

2826
func provideDefaults() (out, error) {
@@ -37,21 +35,20 @@ func provideDefaults() (out, error) {
3735

3836
return out{
3937
Factory: touchstone.NewFactory(cfg, zap.NewNop(), pr),
40-
BasicClientConfig: chrysom.BasicClientConfig{
41-
Address: "example.com",
42-
Bucket: "bucket-name",
38+
ClientOptions: chrysom.ClientOptions{
39+
chrysom.Bucket("bucket-name"),
4340
},
44-
GetLogger: func(context.Context) *zap.Logger { return zap.NewNop() },
45-
SetLogger: func(context.Context, *zap.Logger) context.Context { return context.Background() },
41+
// Listener has no required options
42+
ListenerOptions: chrysom.ListenerOptions{},
4643
}, nil
4744
}
4845

4946
func TestProvide(t *testing.T) {
5047
t.Run("Test anclafx.Provide() defaults", func(t *testing.T) {
5148
var (
52-
svc ancla.Service
53-
bc *chrysom.BasicClient
54-
l *chrysom.ListenerClient
49+
svc ancla.Service
50+
pushReader chrysom.PushReader
51+
listener *chrysom.ListenerClient
5552
)
5653

5754
app := fxtest.New(t,
@@ -61,8 +58,8 @@ func TestProvide(t *testing.T) {
6158
),
6259
fx.Populate(
6360
&svc,
64-
&bc,
65-
&l,
61+
&pushReader,
62+
&listener,
6663
),
6764
)
6865

@@ -71,8 +68,8 @@ func TestProvide(t *testing.T) {
7168
require.NoError(app.Err())
7269
app.RequireStart()
7370
require.NotNil(svc)
74-
require.NotNil(bc)
75-
require.NotNil(l)
71+
require.NotNil(pushReader)
72+
require.NotNil(listener)
7673
app.RequireStop()
7774
})
7875
}

auth/acquire.go

+6
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,9 @@ type Decorator interface {
1313
// Decorate decorates the given http request with authorization header(s).
1414
Decorate(ctx context.Context, req *http.Request) error
1515
}
16+
17+
type DecoratorFunc func(context.Context, *http.Request) error
18+
19+
func (f DecoratorFunc) Decorate(ctx context.Context, req *http.Request) error { return f(ctx, req) }
20+
21+
var Nop = DecoratorFunc(func(context.Context, *http.Request) error { return nil })

auth/context_test.go

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// SPDX-FileCopyrightText: 2025 Comcast Cable Communications Management, LLC
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package auth
5+
6+
import (
7+
"context"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
func TestPrincipal(t *testing.T) {
14+
t.Run("Test SetPartnerIDs, GetPartnerIDs", func(t *testing.T) {
15+
assert := assert.New(t)
16+
partnerIDs := []string{"foo", "bar"}
17+
ctx := SetPartnerIDs(context.Background(), partnerIDs)
18+
actualPartnerIDs, ok := GetPartnerIDs(ctx)
19+
assert.True(ok)
20+
assert.Equal(partnerIDs, actualPartnerIDs)
21+
actualPartnerIDs, ok = GetPartnerIDs(context.Background())
22+
assert.False(ok)
23+
var empty []string
24+
assert.Equal(empty, actualPartnerIDs)
25+
})
26+
t.Run("Test SetPrincipal, GetPrincipal", func(t *testing.T) {
27+
assert := assert.New(t)
28+
principal := "foo"
29+
ctx := SetPrincipal(context.Background(), principal)
30+
actualPrincipal, ok := GetPrincipal(ctx)
31+
assert.True(ok)
32+
assert.Equal(principal, actualPrincipal)
33+
actualPrincipal, ok = GetPrincipal(context.Background())
34+
assert.False(ok)
35+
assert.Equal("", actualPrincipal)
36+
})
37+
}

chrysom/basicClient.go

+30-71
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"fmt"
1212
"io"
1313
"net/http"
14-
"time"
1514

1615
"github.com/xmidt-org/ancla/auth"
1716
"github.com/xmidt-org/ancla/model"
@@ -25,53 +24,28 @@ const (
2524
)
2625

2726
var (
28-
ErrNilMeasures = errors.New("measures cannot be nil")
29-
ErrAddressEmpty = errors.New("argus address is required")
30-
ErrBucketEmpty = errors.New("bucket name is required")
31-
ErrItemIDEmpty = errors.New("item ID is required")
32-
ErrItemDataEmpty = errors.New("data field in item is required")
33-
ErrUndefinedIntervalTicker = errors.New("interval ticker is nil. Can't listen for updates")
34-
ErrAuthDecoratorFailure = errors.New("failed decorating auth header")
35-
ErrBadRequest = errors.New("argus rejected the request as invalid")
27+
ErrItemIDEmpty = errors.New("item ID is required")
28+
ErrItemDataEmpty = errors.New("data field in item is required")
29+
ErrAuthDecoratorFailure = errors.New("failed decorating auth header")
30+
ErrBadRequest = errors.New("argus rejected the request as invalid")
3631
)
3732

3833
var (
39-
errNonSuccessResponse = errors.New("argus responded with a non-success status code")
40-
errNewRequestFailure = errors.New("failed creating an HTTP request")
41-
errDoRequestFailure = errors.New("http client failed while sending request")
42-
errReadingBodyFailure = errors.New("failed while reading http response body")
43-
errJSONUnmarshal = errors.New("failed unmarshaling JSON response payload")
44-
errJSONMarshal = errors.New("failed marshaling item as JSON payload")
45-
errFailedConfig = errors.New("ancla configuration error")
34+
ErrFailedAuthentication = errors.New("failed to authentication with argus")
35+
errNonSuccessResponse = errors.New("argus responded with a non-success status code")
36+
errNewRequestFailure = errors.New("failed creating an HTTP request")
37+
errDoRequestFailure = errors.New("http client failed while sending request")
38+
errReadingBodyFailure = errors.New("failed while reading http response body")
39+
errJSONUnmarshal = errors.New("failed unmarshaling JSON response payload")
40+
errJSONMarshal = errors.New("failed marshaling item as JSON payload")
4641
)
4742

48-
// BasicClientConfig contains config data for the client that will be used to
49-
// make requests to the Argus client.
50-
type BasicClientConfig struct {
51-
// Address is the Argus URL (i.e. https://example-argus.io:8090)
52-
Address string
53-
54-
// Bucket partition to be used by this client.
55-
Bucket string
56-
57-
// HTTPClient refers to the client that will be used to send requests.
58-
// (Optional) Defaults to http.DefaultClient.
59-
HTTPClient *http.Client
60-
61-
// Auth provides the mechanism to add auth headers to outgoing requests.
62-
// (Optional) If not provided, no auth headers are added.
63-
Auth auth.Decorator
64-
65-
// PullInterval is how often listeners should get updates.
66-
// (Optional). Defaults to 5 seconds.
67-
PullInterval time.Duration
68-
}
69-
7043
// BasicClient is the client used to make requests to Argus.
7144
type BasicClient struct {
7245
client *http.Client
7346
auth auth.Decorator
7447
storeBaseURL string
48+
storeAPIPath string
7549
bucket string
7650
getLogger func(context.Context) *zap.Logger
7751
}
@@ -83,31 +57,32 @@ type response struct {
8357
}
8458

8559
const (
86-
storeAPIPath = "/api/v1/store"
60+
storeV1APIPath = "/api/v1/store"
8761
errWrappedFmt = "%w: %s"
8862
errStatusCodeFmt = "%w: received status %v"
8963
errorHeaderKey = "errorHeader"
9064
)
9165

92-
// Items is a slice of model.Item(s) .
93-
type Items []model.Item
94-
9566
// NewBasicClient creates a new BasicClient that can be used to
9667
// make requests to Argus.
97-
func NewBasicClient(config BasicClientConfig,
98-
getLogger func(context.Context) *zap.Logger) (*BasicClient, error) {
99-
err := validateBasicConfig(&config)
100-
if err != nil {
101-
return nil, err
102-
}
68+
func NewBasicClient(opts ...ClientOption) (*BasicClient, error) {
69+
var (
70+
client BasicClient
71+
defaultClientOptions = ClientOptions{
72+
// localhost defaults
73+
StoreBaseURL(""),
74+
StoreAPIPath(""),
75+
// Nop defaults
76+
HTTPClient(nil),
77+
GetClientLogger(nil),
78+
Auth(nil),
79+
}
80+
)
10381

104-
return &BasicClient{
105-
client: config.HTTPClient,
106-
auth: config.Auth,
107-
bucket: config.Bucket,
108-
storeBaseURL: config.Address + storeAPIPath,
109-
getLogger: getLogger,
110-
}, nil
82+
opts = append(defaultClientOptions, opts...)
83+
opts = append(opts, clientValidator())
84+
85+
return &client, ClientOptions(opts).apply(&client)
11186
}
11287

11388
// GetItems fetches all items that belong to a given owner.
@@ -251,19 +226,3 @@ func translateNonSuccessStatusCode(code int) error {
251226
return errNonSuccessResponse
252227
}
253228
}
254-
255-
func validateBasicConfig(config *BasicClientConfig) error {
256-
if config.Address == "" {
257-
return ErrAddressEmpty
258-
}
259-
260-
if config.Bucket == "" {
261-
return ErrBucketEmpty
262-
}
263-
264-
if config.HTTPClient == nil {
265-
config.HTTPClient = http.DefaultClient
266-
}
267-
268-
return nil
269-
}

0 commit comments

Comments
 (0)