mcfg: make ParamValue not embed Param, so that a Source which is []ParamValue makes sense, and can replace SourceMap
This commit is contained in:
parent
ddd26259b2
commit
8e2cffd65b
21
mcfg/cli.go
21
mcfg/cli.go
@ -58,7 +58,7 @@ func (cli SourceCLI) Parse(params []Param) ([]ParamValue, error) {
|
|||||||
pvs := make([]ParamValue, 0, len(args))
|
pvs := make([]ParamValue, 0, len(args))
|
||||||
var (
|
var (
|
||||||
key string
|
key string
|
||||||
pv ParamValue
|
p Param
|
||||||
pvOk bool
|
pvOk bool
|
||||||
pvStrVal string
|
pvStrVal string
|
||||||
pvStrValOk bool
|
pvStrValOk bool
|
||||||
@ -72,7 +72,7 @@ func (cli SourceCLI) Parse(params []Param) ([]ParamValue, error) {
|
|||||||
os.Stdout.Sync()
|
os.Stdout.Sync()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
} else {
|
} else {
|
||||||
for key, pv.Param = range pM {
|
for key, p = range pM {
|
||||||
if arg == key {
|
if arg == key {
|
||||||
pvOk = true
|
pvOk = true
|
||||||
break
|
break
|
||||||
@ -88,8 +88,7 @@ func (cli SourceCLI) Parse(params []Param) ([]ParamValue, error) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if !pvOk {
|
if !pvOk {
|
||||||
err := merr.New("unexpected config parameter")
|
return nil, merr.New("unexpected config parameter", "param", arg)
|
||||||
return nil, merr.WithValue(err, "param", arg, true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,7 +96,7 @@ func (cli SourceCLI) Parse(params []Param) ([]ParamValue, error) {
|
|||||||
|
|
||||||
// As a special case for CLI, if a boolean has no value set it means it
|
// As a special case for CLI, if a boolean has no value set it means it
|
||||||
// is true.
|
// is true.
|
||||||
if pv.IsBool && !pvStrValOk {
|
if p.IsBool && !pvStrValOk {
|
||||||
pvStrVal = "true"
|
pvStrVal = "true"
|
||||||
pvStrValOk = true
|
pvStrValOk = true
|
||||||
} else if !pvStrValOk {
|
} else if !pvStrValOk {
|
||||||
@ -107,18 +106,20 @@ func (cli SourceCLI) Parse(params []Param) ([]ParamValue, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
pv.Value = pv.Param.fuzzyParse(pvStrVal)
|
pvs = append(pvs, ParamValue{
|
||||||
|
Name: p.Name,
|
||||||
|
Path: p.Path,
|
||||||
|
Value: p.fuzzyParse(pvStrVal),
|
||||||
|
})
|
||||||
|
|
||||||
pvs = append(pvs, pv)
|
|
||||||
key = ""
|
key = ""
|
||||||
pv = ParamValue{}
|
p = Param{}
|
||||||
pvOk = false
|
pvOk = false
|
||||||
pvStrVal = ""
|
pvStrVal = ""
|
||||||
pvStrValOk = false
|
pvStrValOk = false
|
||||||
}
|
}
|
||||||
if pvOk && !pvStrValOk {
|
if pvOk && !pvStrValOk {
|
||||||
err := merr.New("param expected a value")
|
return nil, merr.New("param expected a value", "param", key)
|
||||||
return nil, merr.WithValue(err, "param", key, true)
|
|
||||||
}
|
}
|
||||||
return pvs, nil
|
return pvs, nil
|
||||||
}
|
}
|
||||||
|
@ -57,13 +57,13 @@ func (env SourceEnv) Parse(params []Param) ([]ParamValue, error) {
|
|||||||
for _, kv := range kvs {
|
for _, kv := range kvs {
|
||||||
split := strings.SplitN(kv, "=", 2)
|
split := strings.SplitN(kv, "=", 2)
|
||||||
if len(split) != 2 {
|
if len(split) != 2 {
|
||||||
err := merr.New("malformed environment key/value pair")
|
return nil, merr.New("malformed environment key/value pair", "kv", kv)
|
||||||
return nil, merr.WithValue(err, "kv", kv, true)
|
|
||||||
}
|
}
|
||||||
k, v := split[0], split[1]
|
k, v := split[0], split[1]
|
||||||
if p, ok := pM[k]; ok {
|
if p, ok := pM[k]; ok {
|
||||||
pvs = append(pvs, ParamValue{
|
pvs = append(pvs, ParamValue{
|
||||||
Param: p,
|
Name: p.Name,
|
||||||
|
Path: p.Path,
|
||||||
Value: p.fuzzyParse(v),
|
Value: p.fuzzyParse(v),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
52
mcfg/mcfg.go
52
mcfg/mcfg.go
@ -3,7 +3,10 @@
|
|||||||
package mcfg
|
package mcfg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/mediocregopher/mediocre-go-lib/mctx"
|
"github.com/mediocregopher/mediocre-go-lib/mctx"
|
||||||
@ -75,9 +78,31 @@ func collectParams(ctx mctx.Context) []Param {
|
|||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func paramHash(path []string, name string) string {
|
||||||
|
h := md5.New()
|
||||||
|
for _, pathEl := range path {
|
||||||
|
fmt.Fprintf(h, "pathEl:%q\n", pathEl)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(h, "name:%q\n", name)
|
||||||
|
hStr := hex.EncodeToString(h.Sum(nil))
|
||||||
|
// we add the displayName to it to make debugging easier
|
||||||
|
return paramFullName(path, name) + "/" + hStr
|
||||||
|
}
|
||||||
|
|
||||||
func populate(params []Param, src Source) error {
|
func populate(params []Param, src Source) error {
|
||||||
if src == nil {
|
if src == nil {
|
||||||
src = SourceMap{}
|
src = ParamValues(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// map Params to their hash, so we can match them to their ParamValues
|
||||||
|
// later. There should not be any duplicates here.
|
||||||
|
pM := map[string]Param{}
|
||||||
|
for _, p := range params {
|
||||||
|
hash := paramHash(p.Path, p.Name)
|
||||||
|
if _, ok := pM[hash]; ok {
|
||||||
|
panic("duplicate Param: " + paramFullName(p.Path, p.Name))
|
||||||
|
}
|
||||||
|
pM[hash] = p
|
||||||
}
|
}
|
||||||
|
|
||||||
pvs, err := src.Parse(params)
|
pvs, err := src.Parse(params)
|
||||||
@ -86,24 +111,31 @@ func populate(params []Param, src Source) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// dedupe the ParamValues based on their hashes, with the last ParamValue
|
// dedupe the ParamValues based on their hashes, with the last ParamValue
|
||||||
// taking precedence
|
// taking precedence. Also filter out those with no corresponding Param.
|
||||||
pvM := map[string]ParamValue{}
|
pvM := map[string]ParamValue{}
|
||||||
for _, pv := range pvs {
|
for _, pv := range pvs {
|
||||||
pvM[pv.hash()] = pv
|
hash := paramHash(pv.Path, pv.Name)
|
||||||
|
if _, ok := pM[hash]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pvM[hash] = pv
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for required params
|
// check for required params
|
||||||
for _, param := range params {
|
for hash, p := range pM {
|
||||||
if !param.Required {
|
if !p.Required {
|
||||||
continue
|
continue
|
||||||
} else if _, ok := pvM[param.hash()]; !ok {
|
} else if _, ok := pvM[hash]; !ok {
|
||||||
err := merr.New("required parameter is not set")
|
return merr.New("required parameter is not set",
|
||||||
return merr.WithValue(err, "param", param.fullName(), true)
|
"param", paramFullName(p.Path, p.Name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pv := range pvM {
|
// do the actual populating
|
||||||
if err := json.Unmarshal(pv.Value, pv.Into); err != nil {
|
for hash, pv := range pvM {
|
||||||
|
// at this point, all ParamValues in pvM have a corresponding pM Param
|
||||||
|
p := pM[hash]
|
||||||
|
if err := json.Unmarshal(pv.Value, p.Into); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package mcfg
|
package mcfg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
@ -49,6 +47,10 @@ type Param struct {
|
|||||||
Path []string
|
Path []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func paramFullName(path []string, name string) string {
|
||||||
|
return strings.Join(append(path, name), "-")
|
||||||
|
}
|
||||||
|
|
||||||
func (p Param) fuzzyParse(v string) json.RawMessage {
|
func (p Param) fuzzyParse(v string) json.RawMessage {
|
||||||
if p.IsBool {
|
if p.IsBool {
|
||||||
if v == "" || v == "0" || v == "false" {
|
if v == "" || v == "0" || v == "false" {
|
||||||
@ -63,21 +65,6 @@ func (p Param) fuzzyParse(v string) json.RawMessage {
|
|||||||
return json.RawMessage(v)
|
return json.RawMessage(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Param) fullName() string {
|
|
||||||
return strings.Join(append(p.Path, p.Name), "-")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Param) hash() string {
|
|
||||||
h := md5.New()
|
|
||||||
for _, path := range p.Path {
|
|
||||||
fmt.Fprintf(h, "pathEl:%q\n", path)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(h, "name:%q\n", p.Name)
|
|
||||||
hStr := hex.EncodeToString(h.Sum(nil))
|
|
||||||
// we add the displayName to it to make debugging easier
|
|
||||||
return p.fullName() + "/" + hStr
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustAdd adds the given Param to the mctx.Context. It will panic if a Param of
|
// MustAdd adds the given Param to the mctx.Context. It will panic if a Param of
|
||||||
// the same Name already exists in the mctx.Context.
|
// the same Name already exists in the mctx.Context.
|
||||||
func MustAdd(ctx mctx.Context, param Param) {
|
func MustAdd(ctx mctx.Context, param Param) {
|
||||||
|
@ -7,7 +7,8 @@ import (
|
|||||||
// ParamValue describes a value for a parameter which has been parsed by a
|
// ParamValue describes a value for a parameter which has been parsed by a
|
||||||
// Source.
|
// Source.
|
||||||
type ParamValue struct {
|
type ParamValue struct {
|
||||||
Param
|
Name string
|
||||||
|
Path []string
|
||||||
Value json.RawMessage
|
Value json.RawMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,11 +19,22 @@ type ParamValue struct {
|
|||||||
// by the configuration source.
|
// by the configuration source.
|
||||||
//
|
//
|
||||||
// The returned []ParamValue may contain duplicates of the same Param's value.
|
// The returned []ParamValue may contain duplicates of the same Param's value.
|
||||||
// in which case the later value takes precedence.
|
// in which case the later value takes precedence. It may also contain
|
||||||
|
// ParamValues which do not correspond to any of the passed in Params. These
|
||||||
|
// will be ignored in Populate.
|
||||||
type Source interface {
|
type Source interface {
|
||||||
Parse([]Param) ([]ParamValue, error)
|
Parse([]Param) ([]ParamValue, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParamValues is simply a slice of ParamValue elements, which implements Parse
|
||||||
|
// by always returning itself as-is.
|
||||||
|
type ParamValues []ParamValue
|
||||||
|
|
||||||
|
// Parse implements the method for the Source interface.
|
||||||
|
func (pvs ParamValues) Parse([]Param) ([]ParamValue, error) {
|
||||||
|
return pvs, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Sources combines together multiple Source instances into one. It will call
|
// Sources combines together multiple Source instances into one. It will call
|
||||||
// Parse on each element individually. Values from later Sources take precedence
|
// Parse on each element individually. Values from later Sources take precedence
|
||||||
// over previous ones.
|
// over previous ones.
|
||||||
@ -40,24 +52,3 @@ func (ss Sources) Parse(params []Param) ([]ParamValue, error) {
|
|||||||
}
|
}
|
||||||
return pvs, nil
|
return pvs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SourceMap implements the Source interface by mapping parameter names to
|
|
||||||
// values for them. The names are comprised of the path and name of a Param
|
|
||||||
// joined by "-" characters, i.e. `strings.Join(append(param.Path, param.Name),
|
|
||||||
// "-")`. Values will be parsed in the same way that SourceEnv parses its
|
|
||||||
// variables.
|
|
||||||
type SourceMap map[string]string
|
|
||||||
|
|
||||||
// Parse implements the method for the Source interface.
|
|
||||||
func (m SourceMap) Parse(params []Param) ([]ParamValue, error) {
|
|
||||||
pvs := make([]ParamValue, 0, len(m))
|
|
||||||
for _, p := range params {
|
|
||||||
if v, ok := m[p.fullName()]; ok {
|
|
||||||
pvs = append(pvs, ParamValue{
|
|
||||||
Param: p,
|
|
||||||
Value: p.fuzzyParse(v),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pvs, nil
|
|
||||||
}
|
|
||||||
|
@ -109,7 +109,7 @@ func (scs srcCommonState) applyCtxAndPV(p srcCommonParams) srcCommonState {
|
|||||||
ctxP = get(thisCtx).params[p.name] // get it back out to get any added fields
|
ctxP = get(thisCtx).params[p.name] // get it back out to get any added fields
|
||||||
|
|
||||||
if !p.unset {
|
if !p.unset {
|
||||||
pv := ParamValue{Param: ctxP}
|
pv := ParamValue{Name: ctxP.Name, Path: ctxP.Path}
|
||||||
if p.isBool {
|
if p.isBool {
|
||||||
pv.Value = json.RawMessage("true")
|
pv.Value = json.RawMessage("true")
|
||||||
} else {
|
} else {
|
||||||
@ -159,17 +159,17 @@ func TestSources(t *T) {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSourceMap(t *T) {
|
func TestSourceParamValues(t *T) {
|
||||||
ctx := mctx.New()
|
ctx := mctx.New()
|
||||||
a := RequiredInt(ctx, "a", "")
|
a := RequiredInt(ctx, "a", "")
|
||||||
foo := mctx.ChildOf(ctx, "foo")
|
foo := mctx.ChildOf(ctx, "foo")
|
||||||
b := RequiredString(foo, "b", "")
|
b := RequiredString(foo, "b", "")
|
||||||
c := Bool(foo, "c", "")
|
c := Bool(foo, "c", "")
|
||||||
|
|
||||||
err := Populate(ctx, SourceMap{
|
err := Populate(ctx, ParamValues{
|
||||||
"a": "4",
|
{Name: "a", Value: json.RawMessage(`4`)},
|
||||||
"foo-b": "bbb",
|
{Path: []string{"foo"}, Name: "b", Value: json.RawMessage(`"bbb"`)},
|
||||||
"foo-c": "1",
|
{Path: []string{"foo"}, Name: "c", Value: json.RawMessage("true")},
|
||||||
})
|
})
|
||||||
massert.Fatal(t, massert.All(
|
massert.Fatal(t, massert.All(
|
||||||
massert.Nil(err),
|
massert.Nil(err),
|
||||||
|
Loading…
Reference in New Issue
Block a user