merge.go (443 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package ucfg import ( "fmt" "reflect" "regexp" "time" "unicode" "unicode/utf8" ) // Merge a map, a slice, a struct or another Config object into c. // // Merge traverses the value from recursively copying all values into a hierarchy // of Config objects plus primitives into c. // // Merge supports the options: PathSep, MetaData, StructTag, VarExp, ReplaceValues, AppendValues, PrependValues // // Merge uses the type-dependent default encodings: // - Boolean values are encoded as booleans. // - Integer are encoded as int64 values, unsigned integer values as uint64 and // floats as float64 values. // - Strings are copied into string values. // If the VarExp is set, string fields will be parsed into // variable expansion expressions. The expression can reference any // other setting by absolute name. // - Array and slices are copied into new Config objects with index accessors only. // - Struct values and maps with key type string are encoded as Config objects with // named field accessors. // - Config objects will be copied and added to the current hierarchy. // // The `config` struct tag (configurable via StructTag option) can be used to // set the field name and enable additional merging settings per field: // // // field appears in Config as key "myName" // Field int `config:"myName"` // // // field appears in sub-Config "mySub" as key "myName" (requires PathSep(".")) // Field int `config:"mySub.myName"` // // // field is processed as if keys are part of outer struct (type can be a // // struct, a slice, an array, a map or of type *Config) // Field map[string]interface{} `config:",inline"` // // // field is ignored by Merge // Field string `config:",ignore"` // // Returns an error if merging fails to normalize and validate the from value. // If duplicate setting names are detected in the input, merging fails as well. // // Config cannot represent cyclic structures and Merge does not handle them // well. Passing cyclic structures to Merge will result in an infinite recursive // loop. func (c *Config) Merge(from interface{}, options ...Option) error { // from is empty in case of empty config file if from == nil { return nil } opts := makeOptions(options) other, err := normalize(opts, from) if err != nil { return err } return mergeConfig(opts, c, other) } func mergeConfig(opts *options, to, from *Config) Error { if err := mergeConfigDict(opts, to, from); err != nil { return err } return mergeConfigArr(opts, to, from) } func mergeConfigDict(opts *options, to, from *Config) Error { dict := from.fields.dict() if len(dict) == 0 { return nil } ok := false if opts.configValueHandling == cfgReplaceValue { old := to.fields.dict() to.fields.d = nil defer func() { if !ok { to.fields.d = old } }() } for k, v := range dict { ctx := context{ parent: cfgSub{to}, field: k, } old, _ := to.fields.get(k) opts, err := fieldOptsOverride(opts, k, -1) if err != nil { return err } merged, err := mergeValues(opts, old, v) if err != nil { return err } to.fields.set(k, merged.cpy(ctx)) } ok = true return nil } func mergeConfigArr(opts *options, to, from *Config) Error { currHandling := opts.configValueHandling opts, err := fieldOptsOverride(opts, "*", -1) if err != nil { return err } switch currHandling { case cfgReplaceValue, cfgArrReplaceValue: return mergeConfigReplaceArr(opts, to, from) case cfgArrPrepend: return mergeConfigPrependArr(opts, to, from) case cfgArrAppend: return mergeConfigAppendArr(opts, to, from) case cfgDefaultHandling, cfgMergeValues: return mergeConfigMergeArr(opts, to, from) default: return mergeConfigMergeArr(opts, to, from) } } func mergeConfigReplaceArr(opts *options, to, from *Config) Error { a := from.fields.array() if len(a) == 0 { return nil } var parent value = cfgSub{to} var fields = fields{ d: to.fields.d, a: make([]value, 0, len(a)), } fields.append(parent, a) *to.fields = fields return nil } func mergeConfigMergeArr(opts *options, to, from *Config) Error { l := len(to.fields.array()) arr := from.fields.array() if l > len(arr) { l = len(arr) } var parent value = cfgSub{to} // merge array indexes available in to and from for i := 0; i < l; i++ { ctx := context{ parent: parent, field: fmt.Sprintf("%v", i), } // possible for individual index to be replaced idxOpts, err := fieldOptsOverride(opts, "", i) if err != nil { return err } old := to.fields.array()[i] merged, err := mergeValues(idxOpts, old, arr[i]) if err != nil { return err } to.fields.setAt(i, parent, merged.cpy(ctx)) } if len(arr) > l { // add additional array entries not yet in 'to' to.fields.append(parent, arr[l:]) } return nil } func mergeConfigPrependArr(opts *options, to, from *Config) Error { a1 := to.fields.array() a2 := from.fields.array() if len(a2) == 0 { return nil } var parent value = cfgSub{to} var fields = fields{ d: to.fields.d, a: make([]value, 0, len(a1)+len(a2)), } fields.append(parent, a2) fields.append(parent, a1) *to.fields = fields return nil } func mergeConfigAppendArr(opts *options, to, from *Config) Error { to.fields.append(cfgSub{to}, from.fields.array()) return nil } func mergeValues(opts *options, old, v value) (value, Error) { if old == nil { return v, nil } // check if new and old value evaluate to sub-configurations. If one is no // sub-configuration, use new value only. subOld, err := old.toConfig(opts) if err != nil { return v, nil } subV, err := v.toConfig(opts) if err != nil { return v, nil } // merge new and old evaluated sub-configurations and return subOld for // reassigning to old key in case of subOld being generated dynamically if err := mergeConfig(opts, subOld, subV); err != nil { return nil, err } return cfgSub{subOld}, nil } // convert from into normalized *Config checking for errors // before merging generated(normalized) config with current config func normalize(opts *options, from interface{}) (*Config, Error) { vFrom := chaseValue(reflect.ValueOf(from)) switch vFrom.Type() { case tConfig: return vFrom.Addr().Interface().(*Config), nil case tConfigMap: return normalizeMap(opts, vFrom) default: // try to convert vFrom into Config (rebranding) if v, ok := tryTConfig(vFrom); ok { return v.Addr().Interface().(*Config), nil } // normalize given map/struct value switch vFrom.Kind() { case reflect.Struct: return normalizeStruct(opts, vFrom) case reflect.Map: return normalizeMap(opts, vFrom) case reflect.Array, reflect.Slice: tmp, err := normalizeArray(opts, tagOptions{}, context{}, vFrom) if err != nil { return nil, err } c, _ := tmp.toConfig(opts) return c, nil } } return nil, raiseInvalidTopLevelType(from, opts.meta) } func normalizeMap(opts *options, from reflect.Value) (*Config, Error) { cfg := New() cfg.metadata = opts.meta if err := normalizeMapInto(cfg, opts, from); err != nil { return nil, err } return cfg, nil } func normalizeMapInto(cfg *Config, opts *options, from reflect.Value) Error { k := from.Type().Key().Kind() if k != reflect.String && k != reflect.Interface { return raiseKeyInvalidTypeMerge(cfg, from.Type()) } for _, k := range from.MapKeys() { k = chaseValueInterfaces(k) if k.Kind() != reflect.String { return raiseKeyInvalidTypeMerge(cfg, from.Type()) } err := normalizeSetField(cfg, opts, noTagOpts, k.String(), from.MapIndex(k)) if err != nil { return err } } return nil } func normalizeStruct(opts *options, from reflect.Value) (*Config, Error) { cfg := New() cfg.metadata = opts.meta if err := normalizeStructInto(cfg, opts, from); err != nil { return nil, err } return cfg, nil } func normalizeStructInto(cfg *Config, opts *options, from reflect.Value) Error { v := chaseValue(from) numField := v.NumField() for i := 0; i < numField; i++ { var err Error stField := v.Type().Field(i) // ignore non exported fields if rune, _ := utf8.DecodeRuneInString(stField.Name); !unicode.IsUpper(rune) { continue } name, tagOpts := parseTags(stField.Tag.Get(opts.tag)) if tagOpts.ignore { continue } if tagOpts.squash { vField := chaseValue(v.Field(i)) switch vField.Kind() { case reflect.Struct: err = normalizeStructInto(cfg, opts, vField) case reflect.Map: err = normalizeMapInto(cfg, opts, vField) default: return raiseSquashNeedsObject(cfg, opts, stField.Name, vField.Type()) } } else { name = fieldName(name, stField.Name) err = normalizeSetField(cfg, opts, tagOpts, name, v.Field(i)) } if err != nil { return err } } return nil } func normalizeSetField( cfg *Config, opts *options, tagOpts tagOptions, name string, v reflect.Value, ) Error { val, err := normalizeValue(opts, tagOpts, context{}, v) if err != nil { return err } p := parsePathWithOpts(name, opts) old, err := p.GetValue(cfg, opts) if err != nil { if err.Reason() != ErrMissing { return err } old = nil } switch { case !isNil(old) && isNil(val): return nil case isNil(old): return p.SetValue(cfg, opts, val) case isSub(old) && isSub(val): cfgOld, _ := old.toConfig(opts) cfgVal, _ := val.toConfig(opts) return mergeConfig(opts, cfgOld, cfgVal) default: return raiseDuplicateKey(cfg, name) } } func normalizeStructValue(opts *options, ctx context, from reflect.Value) (value, Error) { sub, err := normalizeStruct(opts, from) if err != nil { return nil, err } v := cfgSub{sub} v.SetContext(ctx) return v, nil } func normalizeMapValue(opts *options, ctx context, from reflect.Value) (value, Error) { sub, err := normalizeMap(opts, from) if err != nil { return nil, err } v := cfgSub{sub} v.SetContext(ctx) return v, nil } func normalizeArray( opts *options, tagOpts tagOptions, ctx context, v reflect.Value, ) (value, Error) { l := v.Len() out := make([]value, 0, l) cfg := New() cfg.metadata = opts.meta cfg.ctx = ctx val := cfgSub{cfg} for i := 0; i < l; i++ { idx := fmt.Sprintf("%v", i) ctx := context{ parent: val, field: idx, } tmp, err := normalizeValue(opts, tagOpts, ctx, v.Index(i)) if err != nil { return nil, err } out = append(out, tmp) } cfg.fields.a = out return val, nil } func normalizeValue( opts *options, tagOpts tagOptions, ctx context, v reflect.Value, ) (value, Error) { v = chaseValue(v) switch v.Type() { case tDuration: d := v.Interface().(time.Duration) return newString(ctx, opts.meta, d.String()), nil case tRegexp: r := v.Addr().Interface().(*regexp.Regexp) return newString(ctx, opts.meta, r.String()), nil } // handle primitives switch v.Kind() { case reflect.Bool: return newBool(ctx, opts.meta, v.Bool()), nil case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: i := v.Int() if i > 0 { return newUint(ctx, opts.meta, uint64(i)), nil } return newInt(ctx, opts.meta, i), nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return newUint(ctx, opts.meta, v.Uint()), nil case reflect.Float32, reflect.Float64: f := v.Float() return newFloat(ctx, opts.meta, f), nil case reflect.String: return normalizeString(ctx, opts, v.String()) case reflect.Array, reflect.Slice: return normalizeArray(opts, tagOpts, ctx, v) case reflect.Map: return normalizeMapValue(opts, ctx, v) case reflect.Struct: if v, ok := tryTConfig(v); ok { c := v.Addr().Interface().(*Config) ret := cfgSub{c} if ret.Context().parent != ctx.parent { ret.SetContext(ctx) } return ret, nil } return normalizeStructValue(opts, ctx, v) default: if v.IsNil() { return &cfgNil{cfgPrimitive{ctx, opts.meta}}, nil } return nil, raiseUnsupportedInputType(ctx, opts.meta, v) } } func normalizeString(ctx context, opts *options, str string) (value, Error) { if !opts.varexp { return newString(ctx, opts.meta, str), nil } varexp, err := parseSplice(str, opts.pathSep, opts.maxIdx, opts.enableNumKeys, opts.escapePath) if err != nil { return nil, raiseParseSplice(ctx, opts.meta, err) } switch p := varexp.(type) { case constExp: return newString(ctx, opts.meta, string(p)), nil case *reference: return newRef(ctx, opts.meta, p), nil } return newSplice(ctx, opts.meta, varexp), nil } func fieldOptsOverride(opts *options, fieldName string, idx int) (*options, Error) { if opts.fieldHandlingTree == nil { return opts, nil } cfgHandling, child, ok := opts.fieldHandlingTree.fieldHandling(fieldName, idx) child, err := includeWildcard(child, opts.fieldHandlingTree) if err != nil { return nil, err } if !ok { // Only return a new `options` when arriving at new nested child. This // combined with optimizations in `includeWildcard` will ensure that only // a new opts will be created and returned when absolutely required. if child != nil && opts.fieldHandlingTree != child { newOpts := *opts newOpts.fieldHandlingTree = child opts = &newOpts } return opts, nil } // Only return a new `options` if absolutely required. if opts.configValueHandling != cfgHandling || opts.fieldHandlingTree != child { newOpts := *opts newOpts.configValueHandling = cfgHandling newOpts.fieldHandlingTree = child opts = &newOpts } return opts, nil } func includeWildcard(child *fieldHandlingTree, parent *fieldHandlingTree) (*fieldHandlingTree, Error) { if parent == nil { return child, nil } wildcard, err := parent.wildcard() if err != nil { return child, nil } if child == nil && len(parent.fields.dict()) == 1 { // parent is already config with just wildcard return parent, nil } sub := newFieldHandlingTree() if child != nil { if err := sub.merge(child); err != nil { return nil, err.(Error) } } if err := sub.setWildcard(wildcard); err != nil { return nil, err.(Error) } return sub, nil }