error.go (243 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 (
"errors"
"fmt"
"reflect"
"runtime/debug"
)
// Error type returned by all public functions in go-ucfg.
type Error interface {
error
// error class, one of ErrConfig, ErrImplementation, ErrUnknown
Class() error
// The internal reason error code like ErrMissing, ErrRequired,
// ErrTypeMismatch and others.
Reason() error
// The error message.
Message() string
// [optional] path of config element error occurred for
Path() string
// [optional] stack trace
Trace() string
}
type baseError struct {
reason error
class error
message string
path string
}
type criticalError struct {
baseError
trace string
}
// Error Reasons
var (
ErrMissing = errors.New("missing field")
ErrNoParse = errors.New("parsing dynamic configs is disabled")
ErrCyclicReference = errors.New("cyclic reference detected")
ErrDuplicateValidator = errors.New("validator already registered")
ErrTypeNoArray = errors.New("field is no array")
ErrTypeMismatch = errors.New("type mismatch")
ErrKeyTypeNotString = errors.New("key must be a string")
ErrIndexOutOfRange = errors.New("out of range index")
ErrPointerRequired = errors.New("pointer required for unpacking configurations")
ErrArraySizeMismatch = errors.New("Array size mismatch")
ErrExpectedObject = errors.New("expected object")
ErrNilConfig = errors.New("config is nil")
ErrNilValue = errors.New("nil value is invalid")
ErrTODO = errors.New("TODO - implement me")
ErrDuplicateKey = errors.New("duplicate key")
ErrOverflow = errors.New("integer overflow")
ErrNegative = errors.New("negative value")
ErrZeroValue = errors.New("zero value")
ErrRequired = errors.New("missing required field")
ErrEmpty = errors.New("empty field")
ErrArrayEmpty = errors.New("empty array")
ErrMapEmpty = errors.New("empty map")
ErrRegexEmpty = errors.New("regex value is not set")
ErrStringEmpty = errors.New("string value is not set")
)
// Error Classes
var (
ErrConfig = errors.New("Configuration error")
ErrImplementation = errors.New("Implementation error")
ErrUnknown = errors.New("Unspecified")
)
func (e baseError) Error() string { return e.Message() }
func (e baseError) Reason() error { return e.reason }
func (e baseError) Class() error { return e.class }
func (e baseError) Trace() string { return "" }
func (e baseError) Path() string { return e.path }
func (e baseError) Message() string {
if e.message == "" {
return e.reason.Error()
}
return e.message
}
func (e criticalError) Trace() string { return e.trace }
func (e criticalError) Error() string {
return fmt.Sprintf("%s\nTrace:%v\n", e.baseError.Message(), e.trace)
}
func raiseErr(reason error, message string) Error {
return baseError{
reason: reason,
message: message,
class: ErrConfig,
}
}
func raiseImplErr(reason error) Error {
return baseError{
reason: reason,
class: ErrImplementation,
}
}
func raiseCritical(reason error, message string) Error {
if message == "" {
message = reason.Error()
}
if message != "" {
message = fmt.Sprintf("(assert) %v", message)
}
return criticalError{
baseError{reason, ErrImplementation, message, ""},
string(debug.Stack()),
}
}
func raisePathErr(reason error, meta *Meta, message, path string) Error {
message = messagePath(reason, meta, message, path)
return baseError{reason, ErrConfig, message, path}
}
func messageMeta(message string, meta *Meta) string {
if meta == nil || meta.Source == "" {
return message
}
return fmt.Sprintf("%v (source:'%v')", message, meta.Source)
}
func messagePath(reason error, meta *Meta, message, path string) string {
if path == "" {
path = "config"
} else {
path = fmt.Sprintf("'%v'", path)
}
if message == "" {
message = reason.Error()
}
message = fmt.Sprintf("%v accessing %v", message, path)
return messageMeta(message, meta)
}
func raiseDuplicateKey(cfg *Config, name string) Error {
return raisePathErr(ErrDuplicateKey, cfg.metadata, "", cfg.PathOf(name, "."))
}
func raiseCyclicErr(field string) Error {
message := fmt.Sprintf("cyclic reference detected for key: '%s'", field)
return baseError{
reason: ErrCyclicReference,
class: ErrConfig,
message: message,
}
}
func raiseMissing(c *Config, field string) Error {
// error reading field from config, as missing in c
return raiseMissingMsg(c, field, "")
}
func raiseMissingMsg(c *Config, field string, message string) Error {
return raisePathErr(ErrMissing, c.metadata, message, c.PathOf(field, "."))
}
func raiseMissingArr(ctx context, meta *Meta, idx int) Error {
message := fmt.Sprintf("no value in array at %v", idx)
return raisePathErr(ErrMissing, meta, message, ctx.path("."))
}
func raiseIndexOutOfBounds(opts *options, value value, idx int) Error {
reason := ErrIndexOutOfRange
ctx := value.Context()
len, _ := value.Len(opts)
message := fmt.Sprintf("index '%v' out of range (length=%v)", idx, len)
return raisePathErr(reason, value.meta(), message, ctx.path("."))
}
func raiseInvalidTopLevelType(v interface{}, meta *Meta) Error {
// could be developers or user fault
t := chaseTypePointers(chaseValue(reflect.ValueOf(v)).Type())
message := fmt.Sprintf("type '%v' is not supported on top level of config, only dictionary or list", t)
return raiseErr(ErrTypeMismatch, messageMeta(message, meta))
}
func raiseKeyInvalidTypeUnpack(t reflect.Type, from *Config) Error {
// most likely developers fault
ctx := from.ctx
reason := ErrKeyTypeNotString
message := fmt.Sprintf("string key required when unpacking into '%v'", t)
return raiseCritical(reason, messagePath(reason, from.metadata, message, ctx.path(".")))
}
func raiseKeyInvalidTypeMerge(cfg *Config, t reflect.Type) Error {
ctx := cfg.ctx
reason := ErrKeyTypeNotString
message := fmt.Sprintf("string key required when merging into '%v'", t)
return raiseCritical(reason, messagePath(reason, cfg.metadata, message, ctx.path(".")))
}
func raiseSquashNeedsObject(cfg *Config, opts *options, f string, t reflect.Type) Error {
reason := ErrTypeMismatch
message := fmt.Sprintf("require map or struct when squash merging '%v' (%v)", f, t)
return raiseCritical(reason, messagePath(reason, opts.meta, message, cfg.Path(".")))
}
func raiseInlineNeedsObject(cfg *Config, f string, t reflect.Type) Error {
reason := ErrTypeMismatch
message := fmt.Sprintf("require map or struct when inling '%v' (%v)", f, t)
return raiseCritical(reason,
messagePath(reason, cfg.metadata, message, cfg.Path(".")))
}
func raiseUnsupportedInputType(ctx context, meta *Meta, v reflect.Value) Error {
reason := ErrTypeMismatch
message := fmt.Sprintf("unspported input type (%v) with value '%#v'",
v.Type(), v)
return raiseCritical(reason, messagePath(reason, meta, message, ctx.path(".")))
}
func raiseNoParse(ctx context, meta *Meta) Error {
reason := ErrNoParse
return raisePathErr(reason, meta, "", ctx.path("."))
}
func raiseNil(reason error) Error {
// programmers error (passed unexpected nil pointer)
return raiseCritical(reason, "")
}
func raisePointerRequired(v reflect.Value) Error {
// developer did not pass pointer, unpack target is not settable
return raiseCritical(ErrPointerRequired, "")
}
func raiseToTypeNotSupported(opts *options, v value, goT reflect.Type) Error {
reason := ErrTypeMismatch
t, _ := v.typ(opts)
message := fmt.Sprintf("value of type '%v' not convertible into unsupported go type '%v'",
t.name, goT)
ctx := v.Context()
return raiseCritical(reason, messagePath(reason, v.meta(), message, ctx.path(".")))
}
func raiseArraySize(ctx context, meta *Meta, n int, to int) Error {
reason := ErrArraySizeMismatch
message := fmt.Sprintf("array of length %v does not meet required length %v",
n, to)
return raisePathErr(reason, meta, message, ctx.path("."))
}
func raiseConversion(opts *options, v value, err error, to string) Error {
ctx := v.Context()
path := ctx.path(".")
t, _ := v.typ(opts)
message := fmt.Sprintf("can not convert '%v' into '%v'", t.name, to)
return raisePathErr(err, v.meta(), message, path)
}
func raiseExpectedObject(opts *options, v value) Error {
ctx := v.Context()
path := ctx.path(".")
t, _ := v.typ(opts)
message := fmt.Sprintf("required 'object', but found '%v' in field '%v'",
t.name, path)
return raiseErr(ErrExpectedObject, messageMeta(message, v.meta()))
}
func raiseInvalidDuration(v value, err error) Error {
ctx := v.Context()
path := ctx.path(".")
return raisePathErr(err, v.meta(), "", path)
}
func raiseValidation(ctx context, meta *Meta, field string, err error) Error {
path := ""
if field == "" {
path = ctx.path(".")
} else {
path = ctx.pathOf(field, ".")
}
return raiseErr(err, messagePath(err, meta, err.Error(), path))
}
func raiseInvalidRegexp(v value, err error) Error {
ctx := v.Context()
path := ctx.path(".")
message := fmt.Sprintf("Failed to compile regular expression with '%v'", err)
return raisePathErr(err, v.meta(), message, path)
}
func raiseParseSplice(ctx context, meta *Meta, err error) Error {
message := fmt.Sprintf("%v parsing splice", err)
return raisePathErr(err, meta, message, ctx.path("."))
}