Skip to content

Commit

Permalink
Add list and map config field types for plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeffail committed Sep 30, 2021
1 parent 4a57ba4 commit 960ca6f
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.

## Unreleased

### Added

- Go API: New config field types `StringMap`, `IntList`, and `IntMap`.

## 3.56.0 - 2021-09-22

### Added
Expand Down
105 changes: 105 additions & 0 deletions public/service/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,37 @@ func NewStringListField(name string) *ConfigField {
}
}

// NewStringMapField describes a new config field consisting of an object of
// arbitrary keys with string values.
func NewStringMapField(name string) *ConfigField {
return &ConfigField{
field: docs.FieldString(name, "").Map(),
}
}

// NewIntField describes a new int type config field.
func NewIntField(name string) *ConfigField {
return &ConfigField{
field: docs.FieldInt(name, ""),
}
}

// NewIntListField describes a new config field consisting of a list of
// integers.
func NewIntListField(name string) *ConfigField {
return &ConfigField{
field: docs.FieldInt(name, "").Array(),
}
}

// NewIntMapField describes a new config field consisting of an object of
// arbitrary keys with integer values.
func NewIntMapField(name string) *ConfigField {
return &ConfigField{
field: docs.FieldInt(name, "").Map(),
}
}

// NewFloatField describes a new float type config field.
func NewFloatField(name string) *ConfigField {
return &ConfigField{
Expand Down Expand Up @@ -434,6 +458,33 @@ func (p *ParsedConfig) FieldStringList(path ...string) ([]string, error) {
return sList, nil
}

// FieldStringMap accesses a field that is an object of arbitrary keys and
// string values from the parsed config by its name and returns the value.
// Returns an error if the field is not found, or is not an object of strings.
//
// This method is not valid when the configuration spec was built around a
// config constructor.
func (p *ParsedConfig) FieldStringMap(path ...string) (map[string]string, error) {
v, exists := p.field(path...)
if !exists {
return nil, fmt.Errorf("field '%v' was not found in the config", p.fullDotPath(path...))
}
iMap, ok := v.(map[string]interface{})
if !ok {
if sMap, ok := v.(map[string]string); ok {
return sMap, nil
}
return nil, fmt.Errorf("expected field '%v' to be a string map, got %T", p.fullDotPath(path...), v)
}
sMap := make(map[string]string, len(iMap))
for k, ev := range iMap {
if sMap[k], ok = ev.(string); !ok {
return nil, fmt.Errorf("expected field '%v' to be a string map, found an element of type %T", p.fullDotPath(path...), ev)
}
}
return sMap, nil
}

// FieldInt accesses an int field from the parsed config by its name and returns
// the value. Returns an error if the field is not found or is not an int.
//
Expand All @@ -451,6 +502,60 @@ func (p *ParsedConfig) FieldInt(path ...string) (int, error) {
return i, nil
}

// FieldIntList accesses a field that is a list of integers from the parsed
// config by its name and returns the value. Returns an error if the field is
// not found, or is not a list of integers.
//
// This method is not valid when the configuration spec was built around a
// config constructor.
func (p *ParsedConfig) FieldIntList(path ...string) ([]int, error) {
v, exists := p.field(path...)
if !exists {
return nil, fmt.Errorf("field '%v' was not found in the config", p.fullDotPath(path...))
}
iList, ok := v.([]interface{})
if !ok {
if sList, ok := v.([]int); ok {
return sList, nil
}
return nil, fmt.Errorf("expected field '%v' to be an integer list, got %T", p.fullDotPath(path...), v)
}
sList := make([]int, len(iList))
for i, ev := range iList {
if sList[i], ok = ev.(int); !ok {
return nil, fmt.Errorf("expected field '%v' to be an integer list, found an element of type %T", p.fullDotPath(path...), ev)
}
}
return sList, nil
}

// FieldIntMap accesses a field that is an object of arbitrary keys and
// integer values from the parsed config by its name and returns the value.
// Returns an error if the field is not found, or is not an object of integers.
//
// This method is not valid when the configuration spec was built around a
// config constructor.
func (p *ParsedConfig) FieldIntMap(path ...string) (map[string]int, error) {
v, exists := p.field(path...)
if !exists {
return nil, fmt.Errorf("field '%v' was not found in the config", p.fullDotPath(path...))
}
iMap, ok := v.(map[string]interface{})
if !ok {
if sMap, ok := v.(map[string]int); ok {
return sMap, nil
}
return nil, fmt.Errorf("expected field '%v' to be an integer map, got %T", p.fullDotPath(path...), v)
}
sMap := make(map[string]int, len(iMap))
for k, ev := range iMap {
if sMap[k], ok = ev.(int); !ok {
return nil, fmt.Errorf("expected field '%v' to be an integer map, found an element of type %T", p.fullDotPath(path...), ev)
}
}
return sMap, nil
}

// FieldFloat accesses a float field from the parsed config by its name and
// returns the value. Returns an error if the field is not found or is not a
// float.
Expand Down
24 changes: 24 additions & 0 deletions public/service/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,9 @@ func TestConfigTypedFields(t *testing.T) {
NewStringField("h"),
NewFloatField("i").Default(13.0),
NewStringListField("j"),
NewStringMapField("k"),
NewIntListField("l"),
NewIntMapField("m"),
),
))

Expand All @@ -250,6 +253,15 @@ c:
j:
- first in list
- second in list
k:
first: one
second: two
l:
- 11
- 12
m:
first: 21
second: 22
`, nil)
require.NoError(t, err)

Expand Down Expand Up @@ -289,6 +301,18 @@ c:
assert.NoError(t, err)
assert.Equal(t, []string{"first in list", "second in list"}, ll)

sm, err := parsedConfig.FieldStringMap("c", "f", "k")
assert.NoError(t, err)
assert.Equal(t, map[string]string{"first": "one", "second": "two"}, sm)

il, err := parsedConfig.FieldIntList("c", "f", "l")
assert.NoError(t, err)
assert.Equal(t, []int{11, 12}, il)

im, err := parsedConfig.FieldIntMap("c", "f", "m")
assert.NoError(t, err)
assert.Equal(t, map[string]int{"first": 21, "second": 22}, im)

// Testing namespaces
nsC := parsedConfig.Namespace("c")
nsFOne := nsC.Namespace("f")
Expand Down

0 comments on commit 960ca6f

Please sign in to comment.