util.go (168 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 (
"reflect"
"strings"
"unicode"
"unicode/utf8"
)
type tagOptions struct {
squash bool
ignore bool
cfgHandling configHandling
}
// configHandling configures the operation to execute if we merge into a struct
// field that holds an unpacked config object.
type configHandling uint8
const (
cfgDefaultHandling configHandling = iota
cfgMergeValues
cfgReplaceValue
cfgArrAppend
cfgArrPrepend
cfgArrReplaceValue
)
var noTagOpts = tagOptions{}
func parseTags(tag string) (string, tagOptions) {
s := strings.Split(tag, ",")
opts := tagOptions{}
for _, opt := range s[1:] {
switch opt {
case "squash", "inline":
opts.squash = true
case "ignore":
opts.ignore = true
case "merge":
opts.cfgHandling = cfgMergeValues
case "replace":
opts.cfgHandling = cfgReplaceValue
case "append":
opts.cfgHandling = cfgArrAppend
case "prepend":
opts.cfgHandling = cfgArrPrepend
}
}
return s[0], opts
}
func fieldName(tagName, structName string) string {
if tagName != "" {
return tagName
}
return strings.ToLower(structName)
}
func chaseValueInterfaces(v reflect.Value) reflect.Value {
for v.Kind() == reflect.Interface && !v.IsNil() {
v = v.Elem()
}
return v
}
func chaseValuePointers(v reflect.Value) reflect.Value {
for v.Kind() == reflect.Ptr && !v.IsNil() {
v = v.Elem()
}
return v
}
func chaseValue(v reflect.Value) reflect.Value {
for (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) && !v.IsNil() {
v = v.Elem()
}
return v
}
func chaseTypePointers(t reflect.Type) reflect.Type {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
return t
}
// tryTConfig tries to convert input value into addressable Config by converting
// to *Config first. If value is convertible to Config, but not addressable a new
// value is allocated in order to guarantee returned value of type Config is
// addressable. Returns false if type value is not convertible to TConfig.
func tryTConfig(value reflect.Value) (reflect.Value, bool) {
v := chaseValue(value)
t := v.Type()
if t == tConfig {
v := pointerize(tConfigPtr, tConfig, v)
return v.Elem(), true
}
if !t.ConvertibleTo(tConfig) {
return reflect.Value{}, false
}
v = pointerize(reflect.PtrTo(v.Type()), v.Type(), v)
if !v.Type().ConvertibleTo(tConfigPtr) {
return reflect.Value{}, false
}
v = v.Convert(tConfigPtr)
return v.Elem(), true
}
func pointerize(t, base reflect.Type, v reflect.Value) reflect.Value {
if t == base {
return v
}
if t.Kind() == reflect.Interface {
return v
}
for t != v.Type() {
if !v.CanAddr() {
tmp := reflect.New(v.Type())
tmp.Elem().Set(v)
v = tmp
} else {
v = v.Addr()
}
}
return v
}
func isInt(k reflect.Kind) bool {
switch k {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return true
default:
return false
}
}
func isUint(k reflect.Kind) bool {
switch k {
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return true
default:
return false
}
}
func isFloat(k reflect.Kind) bool {
switch k {
case reflect.Float32, reflect.Float64:
return true
default:
return false
}
}
type fieldInfo struct {
name string
ftype reflect.Type
value reflect.Value
options *options
tagOptions tagOptions
validatorTags []validatorTag
}
func accessField(structVal reflect.Value, fieldIdx int, opts *options) (fieldInfo, bool, Error) {
stField := structVal.Type().Field(fieldIdx)
// ignore non exported fields
if rune, _ := utf8.DecodeRuneInString(stField.Name); !unicode.IsUpper(rune) {
return fieldInfo{}, true, nil
}
name, tagOpts := parseTags(stField.Tag.Get(opts.tag))
if tagOpts.ignore {
return fieldInfo{}, true, nil
}
// create new context, overwriting configValueHandling for all sub-operations
if tagOpts.cfgHandling != opts.configValueHandling {
tmp := &options{}
*tmp = *opts
tmp.configValueHandling = tagOpts.cfgHandling
opts = tmp
}
validators, err := parseValidatorTags(stField.Tag.Get(opts.validatorTag))
if err != nil {
return fieldInfo{}, false, raiseCritical(err, "")
}
return fieldInfo{
name: fieldName(name, stField.Name),
ftype: stField.Type,
value: structVal.Field(fieldIdx),
options: opts,
tagOptions: tagOpts,
validatorTags: validators,
}, false, nil
}