Skip to content

Commit 62469f8

Browse files
authored
Join + ProvideFromRaw (#1030)
Signed-off-by: Angelo De Caro <[email protected]>
1 parent 8802fff commit 62469f8

File tree

4 files changed

+158
-12
lines changed

4 files changed

+158
-12
lines changed

platform/view/services/config/join.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package config
8+
9+
import (
10+
"strings"
11+
)
12+
13+
// Join joins multiple string components with a dot (.).
14+
// It also trims leading/trailing dots from each component to ensure clean joining.
15+
func Join(components ...string) string {
16+
var validComponents []string
17+
for _, comp := range components {
18+
// Trim leading/trailing dots and spaces from each component
19+
trimmedComp := strings.Trim(comp, " .") // Trim both dots and spaces
20+
if trimmedComp != "" {
21+
validComponents = append(validComponents, trimmedComp)
22+
}
23+
}
24+
return strings.Join(validComponents, ".")
25+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package config
8+
9+
import (
10+
"testing"
11+
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
// TestJoin tests the Join function
16+
func TestJoin(t *testing.T) {
17+
tests := []struct {
18+
name string
19+
components []string
20+
expected string
21+
}{
22+
{
23+
name: "Multiple components",
24+
components: []string{"server", "ports", "http"},
25+
expected: "server.ports.http",
26+
},
27+
{
28+
name: "Single component",
29+
components: []string{"version"},
30+
expected: "version",
31+
},
32+
{
33+
name: "No components",
34+
components: []string{},
35+
expected: "",
36+
},
37+
{
38+
name: "Components with leading/trailing dots",
39+
components: []string{".app", "settings."},
40+
expected: "app.settings",
41+
},
42+
{
43+
name: "Components with internal dots (should not be split)",
44+
components: []string{"my.service", "config"},
45+
expected: "my.service.config",
46+
},
47+
{
48+
name: "Mixed case with empty strings",
49+
components: []string{"parent", "", "child", "grandchild"},
50+
expected: "parent.child.grandchild",
51+
},
52+
{
53+
name: "All empty strings",
54+
components: []string{"", "", ""},
55+
expected: "",
56+
},
57+
{
58+
name: "Components with spaces and dots",
59+
components: []string{" user name ", ".api.v1.", "endpoint "},
60+
expected: "user name.api.v1.endpoint",
61+
},
62+
{
63+
name: "Complex case with various trims",
64+
components: []string{" .key1 ", " key2. ", " key3 ", ".key4."},
65+
expected: "key1.key2.key3.key4",
66+
},
67+
}
68+
69+
for _, tt := range tests {
70+
t.Run(tt.name, func(t *testing.T) {
71+
got := Join(tt.components...)
72+
assert.Equal(t, tt.expected, got, "Join(%v) = %q; want %q", tt.components, got, tt.expected)
73+
})
74+
}
75+
}

platform/view/services/config/provider.go

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818

1919
"github.com/hyperledger-labs/fabric-smart-client/pkg/utils/errors"
2020
"github.com/hyperledger-labs/fabric-smart-client/platform/common/services/logging"
21+
"github.com/hyperledger-labs/fabric-smart-client/platform/common/utils/hash"
2122
"github.com/hyperledger-labs/fabric-smart-client/platform/view/services"
2223
viperutil "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/config/viper"
2324
"github.com/hyperledger-labs/fabric-smart-client/platform/view/services/events"
@@ -45,7 +46,6 @@ type OnMergeConfigEventHandler interface {
4546
type DecodeHookFuncType func(reflect.Type, reflect.Type, interface{}) (interface{}, error)
4647

4748
type Provider struct {
48-
confPath string
4949
Backend *viper.Viper
5050
eventSystem events.EventSystem
5151

@@ -54,10 +54,9 @@ type Provider struct {
5454

5555
func NewProvider(confPath string) (*Provider, error) {
5656
p := &Provider{
57-
confPath: confPath,
5857
eventSystem: simple.NewEventBus(),
5958
}
60-
if err := p.load(); err != nil {
59+
if err := p.loadFromPath(confPath); err != nil {
6160
return nil, err
6261
}
6362

@@ -151,15 +150,31 @@ func (p *Provider) OnMergeConfig(handler OnMergeConfigEventHandler) {
151150
p.eventSystem.Subscribe(MergeConfigEventTopic, &eventListener{handler: handler})
152151
}
153152

154-
func (p *Provider) load() error {
153+
// ProvideFromRaw returns a new Provider whose configuration is loaded from the given byte representation.
154+
// The function expects a valid `yaml` representation.
155+
// The new provider inherit the same config file used by this provider.
156+
func (p *Provider) ProvideFromRaw(raw []byte) (*Provider, error) {
157+
newProvider := &Provider{
158+
eventSystem: simple.NewEventBus(),
159+
}
160+
if err := newProvider.loadFromRaw(raw); err != nil {
161+
return nil, err
162+
}
163+
newProvider.Backend.SetConfigFile(p.ConfigFileUsed())
164+
165+
return newProvider, nil
166+
}
167+
168+
func (p *Provider) loadFromPath(path string) error {
155169
p.Backend = viper.New()
156-
err := p.initViper(p.Backend, CmdRoot)
170+
err := p.initViper(p.Backend, CmdRoot, path)
157171
if err != nil {
158172
return err
159173
}
160174

161175
err = p.Backend.ReadInConfig() // Find and read the config file
162-
if err != nil { // Handle errors reading the config file
176+
if err != nil {
177+
// Handle errors reading the config file
163178
// The version of Viper we use claims the config type isn't supported when in fact the file hasn't been found
164179
// Display a more helpful message to avoid confusing the user.
165180
if strings.Contains(fmt.Sprint(err), "Unsupported Config Type") {
@@ -185,6 +200,22 @@ func (p *Provider) load() error {
185200
return nil
186201
}
187202

203+
func (p *Provider) loadFromRaw(raw []byte) error {
204+
p.Backend = viper.New()
205+
p.Backend.SetConfigType("yaml")
206+
207+
// read configuration
208+
if err := p.Backend.ReadConfig(bytes.NewReader(raw)); err != nil {
209+
return errors.Wrapf(err, "failed to read configuration from raw [%s]", hash.Hashable(raw))
210+
}
211+
// post process
212+
if err := p.substituteEnv(); err != nil {
213+
return err
214+
}
215+
216+
return nil
217+
}
218+
188219
// Manually override keys if the respective environment variable is set, because viper doesn't do
189220
// that for UnmarshalKey values (see https://github.com/spf13/viper/pull/1699).
190221
// Example: CORE_LOGGING_FORMAT sets logging.format.
@@ -260,9 +291,9 @@ func setDeepValue(m map[string]any, keys []string, value any) error {
260291
// the configuration we need. If Backend == nil, we will initialize the global
261292
// Viper instance
262293
// ----------------------------------------------------------------------------------
263-
func (p *Provider) initViper(v *viper.Viper, configName string) error {
264-
if len(p.confPath) != 0 {
265-
AddConfigPath(v, p.confPath)
294+
func (p *Provider) initViper(v *viper.Viper, configName string, confPath string) error {
295+
if len(confPath) != 0 {
296+
AddConfigPath(v, confPath)
266297
}
267298

268299
var altPath = os.Getenv("FSCNODE_CFG_PATH")

platform/view/services/config/provider_test.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,21 @@ func TestReadFile(t *testing.T) {
3333
testMerge(t, p)
3434
}
3535

36+
func TestProvideFromRaw(t *testing.T) {
37+
p, err := NewProvider("./testdata")
38+
assert.NoError(t, err)
39+
40+
// read content of ./testdata/core.yaml
41+
raw, err := os.ReadFile("./testdata/core.yaml")
42+
require.NoError(t, err)
43+
newProvider, err := p.ProvideFromRaw(raw)
44+
require.NoError(t, err)
45+
46+
// newProvider should pass the same tests as p
47+
testBasics(t, newProvider)
48+
testMerge(t, newProvider)
49+
}
50+
3651
func TestEnvSubstitution(t *testing.T) {
3752
_ = os.Setenv("CORE_FSC_KVS_PERSISTENCE_OPTS_DATASOURCE", "new data source")
3853
_ = os.Setenv("CORE_STR", "new=string=with=characters.\\AND.CAPS")
@@ -97,16 +112,16 @@ func testMerge(t *testing.T, p *Provider) {
97112
require.NoError(t, err)
98113
require.NoError(t, p.MergeConfig(merge2Raw))
99114

100-
networkName := p.GetString("fabric.network1.name")
115+
networkName := p.GetString(Join("fabric", "network1", "name"))
101116
assert.Equal(t, "pineapple", networkName)
102117

103118
merge3Raw, err := os.ReadFile("./testdata/merge3.yaml")
104119
require.NoError(t, err)
105120
require.NoError(t, p.MergeConfig(merge3Raw))
106121

107-
networkName = p.GetString("fabric.network1.name")
122+
networkName = p.GetString(Join("fabric", "network1", "name"))
108123
assert.Equal(t, "pineapple", networkName)
109-
networkName = p.GetString("fabric.network2.name")
124+
networkName = p.GetString(Join("fabric", "network2", "name"))
110125
assert.Equal(t, "strawberry", networkName)
111126

112127
testBasics(t, p)

0 commit comments

Comments
 (0)