Skip to content

Commit 577e4fb

Browse files
authored
feat: add DirtyPatch method (#719)
It's fast: ``` BenchmarkSet BenchmarkSet-8 6189 9197403 ns/op BenchmarkDirtyPatch BenchmarkDirtyPatch-8 401864 3057 ns/op ```
1 parent b0b6aa7 commit 577e4fb

File tree

2 files changed

+73
-2
lines changed

2 files changed

+73
-2
lines changed

configx/provider.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,33 @@ func (p *Provider) watchForFileChanges(ctx context.Context, c watcherx.EventChan
314314
}
315315
}
316316

317+
// DirtyPatch patches individual config keys without reloading the full config
318+
//
319+
// WARNING! This method is only useful to override existing keys in string or number
320+
// format. DO NOT use this method to override arrays, maps, or other complex types.
321+
//
322+
// This method DOES NOT validate the config against the config JSON schema. If you
323+
// need to validate the config, use the Set method instead.
324+
//
325+
// This method can not be used to remove keys from the config as that is not
326+
// possible without reloading the full config.
327+
func (p *Provider) DirtyPatch(key string, value any) error {
328+
p.l.Lock()
329+
defer p.l.Unlock()
330+
331+
t := tuple{Key: key, Value: value}
332+
kc := NewKoanfConfmap([]tuple{t})
333+
334+
p.forcedValues = append(p.forcedValues, t)
335+
p.providers = append(p.providers, kc)
336+
337+
if err := p.Koanf.Load(kc, nil, []koanf.Option{}...); err != nil {
338+
return err
339+
}
340+
341+
return nil
342+
}
343+
317344
func (p *Provider) Set(key string, value interface{}) error {
318345
p.l.Lock()
319346
defer p.l.Unlock()

configx/provider_test.go

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import (
2222
"github.com/stretchr/testify/require"
2323
)
2424

25-
func TestProviderMethods(t *testing.T) {
25+
func newProvider(t testing.TB) *Provider {
2626
// Fake some flags
2727
f := pflag.NewFlagSet("config", pflag.ContinueOnError)
2828
f.String("foo-bar-baz", "", "")
@@ -32,10 +32,15 @@ func TestProviderMethods(t *testing.T) {
3232
RegisterFlags(f)
3333

3434
ctx, cancel := context.WithCancel(context.Background())
35-
defer cancel()
35+
t.Cleanup(cancel)
3636

3737
p, err := New(ctx, []byte(`{"type": "object", "properties": {"foo-bar-baz": {"type": "string"}, "b": {"type": "string"}}}`), WithFlags(f), WithContext(ctx))
3838
require.NoError(t, err)
39+
return p
40+
}
41+
42+
func TestProviderMethods(t *testing.T) {
43+
p := newProvider(t)
3944

4045
t.Run("check flags", func(t *testing.T) {
4146
assert.Equal(t, "fff", p.String("foo-bar-baz"))
@@ -106,6 +111,21 @@ func TestProviderMethods(t *testing.T) {
106111
assert.NoError(t, p.Set("nested.value", "https://www.ory.sh/kratos"))
107112
assert.Equal(t, "https://www.ory.sh/kratos", p.Get("nested.value"))
108113
})
114+
115+
t.Run("use DirtyPatch operations", func(t *testing.T) {
116+
assert.NoError(t, p.DirtyPatch("nested", nil))
117+
assert.NoError(t, p.DirtyPatch("nested.value", "https://www.ory.sh/kratos"))
118+
assert.Equal(t, "https://www.ory.sh/kratos", p.Get("nested.value"))
119+
120+
assert.NoError(t, p.DirtyPatch("duration.integer1", -1))
121+
assert.NoError(t, p.DirtyPatch("duration.integer2", "-1"))
122+
assert.Equal(t, -1*time.Nanosecond, p.DurationF("duration.integer1", time.Second))
123+
assert.Equal(t, -1*time.Nanosecond, p.DurationF("duration.integer2", time.Second))
124+
125+
require.NoError(t, p.DirtyPatch("some.float", 123.123))
126+
assert.Equal(t, 123.123, p.Float64F("some.float", 321.321))
127+
assert.Equal(t, 321.321, p.Float64F("not.some.float", 321.321))
128+
})
109129
}
110130

111131
func TestAdvancedConfigs(t *testing.T) {
@@ -212,3 +232,27 @@ func TestAdvancedConfigs(t *testing.T) {
212232
})
213233
}
214234
}
235+
236+
func BenchmarkSet(b *testing.B) {
237+
// Benchmark set function
238+
p := newProvider(b)
239+
var err error
240+
for i := 0; i < b.N; i++ {
241+
err = p.Set("nested.value", "https://www.ory.sh/kratos")
242+
if err != nil {
243+
b.Fatalf("Unexpected error: %s", err)
244+
}
245+
}
246+
}
247+
248+
func BenchmarkDirtyPatch(b *testing.B) {
249+
// Benchmark set function
250+
p := newProvider(b)
251+
var err error
252+
for i := 0; i < b.N; i++ {
253+
err = p.DirtyPatch("nested.value", "https://www.ory.sh/kratos")
254+
if err != nil {
255+
b.Fatalf("Unexpected error: %s", err)
256+
}
257+
}
258+
}

0 commit comments

Comments
 (0)