types.go (502 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"
"math"
"reflect"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/elastic/go-ucfg/parse"
)
type value interface {
typ(opts *options) (typeInfo, error)
cpy(c context) value
Context() context
SetContext(c context)
meta() *Meta
setMeta(m *Meta)
Len(opts *options) (int, error)
reflect(opts *options) (reflect.Value, error)
reify(opts *options) (interface{}, error)
toBool(opts *options) (bool, error)
toString(opts *options) (string, error)
toInt(opts *options) (int64, error)
toUint(opts *options) (uint64, error)
toFloat(opts *options) (float64, error)
toConfig(opts *options) (*Config, error)
canCache() bool
}
type typeInfo struct {
name string
gotype reflect.Type
}
type context struct {
parent value
field string
}
type cfgBool struct {
cfgPrimitive
b bool
}
type cfgInt struct {
cfgPrimitive
i int64
}
type cfgUint struct {
cfgPrimitive
u uint64
}
type cfgFloat struct {
cfgPrimitive
f float64
}
type cfgString struct {
cfgPrimitive
s string
}
type cfgSub struct {
c *Config
}
type cfgNil struct{ cfgPrimitive }
type cfgPrimitive struct {
ctx context
metadata *Meta
}
type cfgDynamic struct {
cfgPrimitive
id cacheID
dyn dynValue
}
type dynValue interface {
getValue(p *cfgPrimitive, opts *options) (value, error)
String() string
}
type refDynValue reference
type spliceDynValue struct {
e varEvaler
}
var spliceSeq int32
func (c *context) empty() bool {
return c.parent == nil
}
func (c *context) getParent() *Config {
if c.parent == nil {
return nil
}
if cfg, ok := c.parent.(cfgSub); ok {
return cfg.c
}
return nil
}
func (c *context) path(sep string) string {
if c.field == "" {
return ""
}
if c.parent != nil {
p := c.parent.Context()
if parent := p.path(sep); parent != "" {
return fmt.Sprintf("%v%v%v", parent, sep, c.field)
}
}
return c.field
}
func (c *context) pathOf(field, sep string) string {
if p := c.path(sep); p != "" {
return fmt.Sprintf("%v%v%v", p, sep, field)
}
return field
}
func newBool(ctx context, m *Meta, b bool) *cfgBool {
return &cfgBool{cfgPrimitive{ctx, m}, b}
}
func newInt(ctx context, m *Meta, i int64) *cfgInt {
return &cfgInt{cfgPrimitive{ctx, m}, i}
}
func newUint(ctx context, m *Meta, u uint64) *cfgUint {
return &cfgUint{cfgPrimitive{ctx, m}, u}
}
func newFloat(ctx context, m *Meta, f float64) *cfgFloat {
return &cfgFloat{cfgPrimitive{ctx, m}, f}
}
func newString(ctx context, m *Meta, s string) *cfgString {
return &cfgString{cfgPrimitive{ctx, m}, s}
}
func newRef(ctx context, m *Meta, ref *reference) *cfgDynamic {
return newDyn(ctx, m, (*refDynValue)(ref))
}
func newSplice(ctx context, m *Meta, s varEvaler) *cfgDynamic {
return newDyn(ctx, m, spliceDynValue{s})
}
func newDyn(ctx context, m *Meta, val dynValue) *cfgDynamic {
seq := atomic.AddInt32(&spliceSeq, 1)
dyn := &cfgDynamic{cfgPrimitive: cfgPrimitive{ctx, m}, dyn: val}
dyn.id = cacheID(fmt.Sprintf("%8X-%4X-%p", time.Now().Unix(), seq, dyn))
return dyn
}
func (p *cfgPrimitive) Context() context { return p.ctx }
func (p *cfgPrimitive) SetContext(c context) { p.ctx = c }
func (p *cfgPrimitive) meta() *Meta { return p.metadata }
func (p *cfgPrimitive) setMeta(m *Meta) { p.metadata = m }
func (cfgPrimitive) Len(*options) (int, error) { return 1, nil }
func (cfgPrimitive) toBool(*options) (bool, error) { return false, ErrTypeMismatch }
func (cfgPrimitive) toString(*options) (string, error) { return "", ErrTypeMismatch }
func (cfgPrimitive) toInt(*options) (int64, error) { return 0, ErrTypeMismatch }
func (cfgPrimitive) toUint(*options) (uint64, error) { return 0, ErrTypeMismatch }
func (cfgPrimitive) toFloat(*options) (float64, error) { return 0, ErrTypeMismatch }
func (cfgPrimitive) toConfig(*options) (*Config, error) { return nil, ErrTypeMismatch }
func (cfgPrimitive) canCache() bool { return true }
func (c *cfgNil) cpy(ctx context) value { return &cfgNil{cfgPrimitive{ctx, c.metadata}} }
func (*cfgNil) Len(*options) (int, error) { return 0, nil }
func (*cfgNil) toString(*options) (string, error) { return "null", nil }
func (*cfgNil) toInt(*options) (int64, error) { return 0, ErrTypeMismatch }
func (*cfgNil) toUint(*options) (uint64, error) { return 0, ErrTypeMismatch }
func (*cfgNil) toFloat(*options) (float64, error) { return 0, ErrTypeMismatch }
func (*cfgNil) reify(*options) (interface{}, error) { return nil, nil }
func (*cfgNil) typ(*options) (typeInfo, error) { return typeInfo{"any", reflect.PtrTo(tConfig)}, nil }
func (c *cfgNil) meta() *Meta { return c.metadata }
func (c *cfgNil) setMeta(m *Meta) { c.metadata = m }
func (c *cfgNil) reflect(opts *options) (reflect.Value, error) {
cfg, _ := c.toConfig(opts)
return reflect.ValueOf(cfg), nil
}
func (c *cfgNil) toConfig(*options) (*Config, error) {
n := New()
n.ctx = c.ctx
return n, nil
}
func (c *cfgBool) cpy(ctx context) value { return newBool(ctx, c.meta(), c.b) }
func (c *cfgBool) toBool(*options) (bool, error) { return c.b, nil }
func (c *cfgBool) reflect(*options) (reflect.Value, error) { return reflect.ValueOf(c.b), nil }
func (c *cfgBool) reify(*options) (interface{}, error) { return c.b, nil }
func (c *cfgBool) toString(*options) (string, error) { return fmt.Sprintf("%t", c.b), nil }
func (c *cfgBool) typ(*options) (typeInfo, error) { return typeInfo{"bool", tBool}, nil }
func (c *cfgInt) cpy(ctx context) value { return newInt(ctx, c.meta(), c.i) }
func (c *cfgInt) toInt(*options) (int64, error) { return c.i, nil }
func (c *cfgInt) toFloat(*options) (float64, error) { return float64(c.i), nil }
func (c *cfgInt) reflect(*options) (reflect.Value, error) { return reflect.ValueOf(c.i), nil }
func (c *cfgInt) reify(*options) (interface{}, error) { return c.i, nil }
func (c *cfgInt) toString(*options) (string, error) { return fmt.Sprintf("%d", c.i), nil }
func (c *cfgInt) typ(*options) (typeInfo, error) { return typeInfo{"int", tInt64}, nil }
func (c *cfgInt) toUint(*options) (uint64, error) {
if c.i < 0 {
return 0, ErrNegative
}
return uint64(c.i), nil
}
func (c *cfgUint) cpy(ctx context) value { return newUint(ctx, c.meta(), c.u) }
func (c *cfgUint) reflect(*options) (reflect.Value, error) { return reflect.ValueOf(c.u), nil }
func (c *cfgUint) reify(*options) (interface{}, error) { return c.u, nil }
func (c *cfgUint) toString(*options) (string, error) { return fmt.Sprintf("%d", c.u), nil }
func (c *cfgUint) typ(*options) (typeInfo, error) { return typeInfo{"uint", tUint64}, nil }
func (c *cfgUint) toUint(*options) (uint64, error) { return c.u, nil }
func (c *cfgUint) toFloat(*options) (float64, error) { return float64(c.u), nil }
func (c *cfgUint) toInt(*options) (int64, error) {
if c.u > math.MaxInt64 {
return 0, ErrOverflow
}
return int64(c.u), nil
}
func (c *cfgFloat) cpy(ctx context) value { return newFloat(ctx, c.meta(), c.f) }
func (c *cfgFloat) toFloat(*options) (float64, error) { return c.f, nil }
func (c *cfgFloat) reflect(*options) (reflect.Value, error) { return reflect.ValueOf(c.f), nil }
func (c *cfgFloat) reify(*options) (interface{}, error) { return c.f, nil }
func (c *cfgFloat) toString(*options) (string, error) { return fmt.Sprintf("%v", c.f), nil }
func (c *cfgFloat) typ(*options) (typeInfo, error) { return typeInfo{"float", tFloat64}, nil }
func (c *cfgFloat) toUint(*options) (uint64, error) {
if c.f < 0 {
return 0, ErrNegative
}
if c.f > math.MaxUint64 {
return 0, ErrOverflow
}
return uint64(c.f), nil
}
func (c *cfgFloat) toInt(*options) (int64, error) {
if c.f < math.MinInt64 || math.MaxInt64 < c.f {
return 0, ErrOverflow
}
return int64(c.f), nil
}
func (c *cfgString) cpy(ctx context) value { return newString(ctx, c.meta(), c.s) }
func (c *cfgString) reflect(*options) (reflect.Value, error) {
return reflect.ValueOf(c.s), nil
}
func (c *cfgString) reify(*options) (interface{}, error) { return c.s, nil }
func (c *cfgString) typ(*options) (typeInfo, error) { return typeInfo{"string", tString}, nil }
func (c *cfgString) toBool(*options) (bool, error) { return strconv.ParseBool(c.s) }
func (c *cfgString) toString(*options) (string, error) { return c.s, nil }
func (c *cfgString) toInt(*options) (int64, error) { return strconv.ParseInt(c.s, 0, 64) }
func (c *cfgString) toUint(*options) (uint64, error) { return strconv.ParseUint(c.s, 0, 64) }
func (c *cfgString) toFloat(*options) (float64, error) { return strconv.ParseFloat(c.s, 64) }
func (c cfgSub) Context() context { return c.c.ctx }
func (cfgSub) toBool(*options) (bool, error) { return false, ErrTypeMismatch }
func (cfgSub) toString(*options) (string, error) { return "", ErrTypeMismatch }
func (cfgSub) toInt(*options) (int64, error) { return 0, ErrTypeMismatch }
func (cfgSub) toUint(*options) (uint64, error) { return 0, ErrTypeMismatch }
func (cfgSub) toFloat(*options) (float64, error) { return 0, ErrTypeMismatch }
func (c cfgSub) toConfig(*options) (*Config, error) { return c.c, nil }
func (c cfgSub) canCache() bool { return false }
func (c cfgSub) Len(*options) (int, error) {
arr := c.c.fields.array()
if arr != nil {
return len(arr), nil
}
return 1, nil
}
func (c cfgSub) typ(*options) (typeInfo, error) {
return typeInfo{"object", reflect.PtrTo(tConfig)}, nil
}
// func (cfgSub) typ() (typeInfo, error) { return typeInfo{"object", reflect.PtrTo(tConfig)}, nil }
func (c cfgSub) reflect(*options) (reflect.Value, error) { return reflect.ValueOf(c.c), nil }
func (c cfgSub) meta() *Meta { return c.c.metadata }
func (c cfgSub) setMeta(m *Meta) { c.c.metadata = m }
func (c cfgSub) cpy(ctx context) value {
newC := cfgSub{
c: &Config{ctx: ctx, metadata: c.c.metadata},
}
dict := c.c.fields.dict()
arr := c.c.fields.array()
fields := &fields{}
for name, f := range dict {
ctx := f.Context()
v := f.cpy(context{field: ctx.field, parent: newC})
fields.set(name, v)
}
if arr != nil {
fields.a = make([]value, len(arr))
for i, f := range arr {
ctx := f.Context()
v := f.cpy(context{field: ctx.field, parent: newC})
fields.setAt(i, newC, v)
}
}
newC.c.fields = fields
return newC
}
func (c cfgSub) SetContext(ctx context) {
if c.c.ctx.empty() {
c.c.ctx = ctx
} else {
c.c = &Config{
ctx: ctx,
fields: c.c.fields,
}
}
}
func (c cfgSub) reify(opts *options) (interface{}, error) {
parentFields := opts.activeFields
defer func() { opts.activeFields = parentFields }()
fields := c.c.fields.dict()
arr := c.c.fields.array()
switch {
case len(fields) == 0 && len(arr) == 0 && arr != nil:
// preserve empty arrays
return []interface{}{}, nil
case len(fields) == 0 && len(arr) == 0:
return nil, nil
case len(fields) > 0 && len(arr) == 0:
m := make(map[string]interface{})
for k, v := range fields {
opts.activeFields = newFieldSet(parentFields)
var err error
if m[k], err = v.reify(opts); err != nil {
return nil, err
}
}
return m, nil
case len(fields) == 0 && len(arr) > 0:
m := make([]interface{}, len(arr))
for i, v := range arr {
opts.activeFields = newFieldSet(parentFields)
var err error
if m[i], err = v.reify(opts); err != nil {
return nil, err
}
}
return m, nil
default:
m := make(map[string]interface{})
for k, v := range fields {
opts.activeFields = newFieldSet(parentFields)
var err error
if m[k], err = v.reify(opts); err != nil {
return nil, err
}
}
for i, v := range arr {
opts.activeFields = newFieldSet(parentFields)
var err error
m[fmt.Sprintf("%d", i)], err = v.reify(opts)
if err != nil {
return nil, err
}
}
return m, nil
}
}
func (d *cfgDynamic) typ(opts *options) (ti typeInfo, err error) {
d.withValue(&err, opts, func(v value) {
ti, err = v.typ(opts)
})
return
}
func (d *cfgDynamic) cpy(c context) value {
return newDyn(c, d.meta(), d.dyn)
}
func (d *cfgDynamic) Len(opts *options) (l int, err error) {
d.withValue(&err, opts, func(v value) {
l, err = v.Len(opts)
})
return
}
func (d *cfgDynamic) reflect(opts *options) (rv reflect.Value, err error) {
d.withValue(&err, opts, func(v value) {
rv, err = v.reflect(opts)
})
return
}
func (d *cfgDynamic) reify(opts *options) (rv interface{}, err error) {
d.withValue(&err, opts, func(v value) {
rv, err = v.reify(opts)
})
return
}
func (d *cfgDynamic) toBool(opts *options) (b bool, err error) {
d.withValue(&err, opts, func(v value) {
b, err = v.toBool(opts)
})
return
}
func (d *cfgDynamic) toString(opts *options) (s string, err error) {
d.withValue(&err, opts, func(v value) {
s, err = v.toString(opts)
})
return
}
func (d *cfgDynamic) toInt(opts *options) (i int64, err error) {
d.withValue(&err, opts, func(v value) {
i, err = v.toInt(opts)
})
return
}
func (d *cfgDynamic) toUint(opts *options) (u uint64, err error) {
d.withValue(&err, opts, func(v value) {
u, err = v.toUint(opts)
})
return
}
func (d *cfgDynamic) toFloat(opts *options) (f float64, err error) {
d.withValue(&err, opts, func(v value) {
f, err = v.toFloat(opts)
})
return
}
func (d *cfgDynamic) toConfig(opts *options) (cfg *Config, err error) {
d.withValue(&err, opts, func(v value) {
cfg, err = v.toConfig(opts)
})
return
}
func (d *cfgDynamic) withValue(err *error, opts *options, fn func(value)) {
var v value
if v, *err = d.getValue(opts); *err == nil {
fn(v)
}
}
func (d *cfgDynamic) getValue(opts *options) (value, error) {
return opts.parsed.cachedValue(d.id, func() (value, error) {
return d.dyn.getValue(&d.cfgPrimitive, opts)
})
}
func (d cfgDynamic) canCache() bool {
return false
}
func (r *refDynValue) String() string {
ref := (*reference)(r)
return ref.String()
}
func (r *refDynValue) getValue(
p *cfgPrimitive,
opts *options,
) (value, error) {
ref := (*reference)(r)
v, err := ref.resolveRef(p.ctx.getParent(), opts)
// If not found or we have a cyclic reference we try the environment resolvers
if v != nil || criticalResolveError(err) {
return v, err
}
previousErr := err
str, parseCfg, err := ref.resolveEnv(p.ctx.getParent(), opts)
if err != nil {
// TODO(ph): Not everything is an Error, will do some cleanup in another PR.
if v, ok := previousErr.(Error); ok {
if v.Reason() == ErrCyclicReference {
return nil, previousErr
}
}
return nil, err
}
return parseValue(p, opts, str, parseCfg)
}
func (s spliceDynValue) getValue(
p *cfgPrimitive,
opts *options,
) (value, error) {
splice := s.e
str, err := splice.eval(p.ctx.getParent(), opts)
if err != nil {
return nil, err
}
return parseValue(p, opts, str, parse.DefaultConfig)
}
func (s spliceDynValue) String() string {
return "<splice>"
}
func parseValue(p *cfgPrimitive, opts *options, str string, parseCfg parse.Config) (value, error) {
if opts.noParse {
return nil, raiseNoParse(p.ctx, p.meta())
}
// only set IgnoreCommas if the default has been changed.
if opts.ignoreCommas {
parseCfg.IgnoreCommas = opts.ignoreCommas
}
ifc, err := parse.ValueWithConfig(str, parseCfg)
if err != nil {
return nil, err
}
if ifc == nil {
if strings.TrimSpace(str) == "" {
return newString(p.ctx, p.meta(), str), nil
}
return &cfgNil{cfgPrimitive{ctx: p.ctx, metadata: p.meta()}}, nil
}
switch v := ifc.(type) {
case bool:
return newBool(p.ctx, p.meta(), v), nil
case int64:
return newInt(p.ctx, p.meta(), v), nil
case uint64:
return newUint(p.ctx, p.meta(), v), nil
case float64:
return newFloat(p.ctx, p.meta(), v), nil
case string:
return newString(p.ctx, p.meta(), v), nil
}
sub, err := normalize(opts, ifc)
if err != nil {
return nil, err
}
sub.ctx = p.ctx
sub.metadata = p.metadata
return cfgSub{sub}, nil
}
func isNil(v value) bool {
if v == nil {
return true
}
_, tst := v.(*cfgNil)
return tst
}
func isSub(v value) bool {
if v == nil {
return false
}
_, tst := v.(cfgSub)
return tst
}