reify.go (634 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"
"regexp"
"time"
)
// Unpack unpacks c into a struct, a map, or a slice allocating maps, slices,
// and pointers as necessary.
//
// Unpack supports the options: PathSep, StructTag, ValidatorTag, Env, Resolve,
// ResolveEnv, ReplaceValues, AppendValues, PrependValues.
//
// When unpacking into a value, Unpack first will try to call Unpack if the
// value implements the Unpacker interface. Otherwise, Unpack tries to convert
// the internal value into the target type:
//
// # Primitive types
//
// bool: requires setting of type bool or string which parses into a
// boolean value (true, false, on, off)
// int(8, 16, 32, 64): requires any number type convertible to int or a string
// parsing to int. Fails if the target value would overflow.
// uint(8, 16, 32, 64): requires any number type convertible to int or a string
// parsing to int. Fails if the target value is negative or would overflow.
// float(32, 64): requires any number type convertible to float or a string
// parsing to float. Fails if the target value is negative or would overflow.
// string: requires any primitive value which is serialized into a string.
//
// # Special types:
//
// time.Duration: requires a number setting converted to seconds or a string
// parsed into time.Duration via time.ParseDuration.
// *regexp.Regexp: requires a string being compiled into a regular expression
// using regexp.Compile.
// *Config: requires a Config object to be stored by pointer into the target
// value. Can be used to capture a sub-Config without interpreting
// the settings yet.
//
// # Arrays/Slices:
//
// Requires a Config object with indexed entries. Named entries will not be
// unpacked into the Array/Slice. Primitive values will be handled like arrays
// of length 1.
//
// # Map
//
// Requires a Config object with all named top-level entries being unpacked into
// the map.
//
// # Struct
//
// Requires a Config object. All named values in the Config object will be unpacked
// into the struct its fields, if the name is available in the struct.
// A field its name is set using the `config` struct tag (configured by StructTag)
// If tag is missing or no field name is configured in the tag, the field name
// itself will be used.
// If the tag sets the `,ignore` flag, the field will not be overwritten.
// If the tag sets the `,inline` or `,squash` flag, Unpack will apply the current
// configuration namespace to the fields.
// If the tag option `replace` is configured, arrays and *ucfg.Config
// convertible fields are replaced by the new values.
// If the tag options `append` or `prepend` is used, arrays will be merged by
// appending/prepending the new array contents.
// The struct tag options `replace`, `append`, and `prepend` overwrites the
// global value merging strategy (e.g. ReplaceValues, AppendValues, ...) for all sub-fields.
//
// When unpacking into a map, primitive, or struct Unpack will call InitDefaults if
// the type implements the Initializer interface. The Initializer interface is not supported
// on arrays or slices. InitDefaults is initialized top-down, meaning that if struct contains
// a map, struct, or primitive that also implements the Initializer interface the contained
// type will be initialized after the struct that contains it. (e.g. if we have
// type A struct { B B }, with both A, and B implementing InitDefaults, then A.InitDefaults
// is called before B.InitDefaults). In the case that a struct contains a pointer to
// a type that implements the Initializer interface and the configuration doesn't contain a
// value for that field then the pointer will not be initialized and InitDefaults will not
// be called.
//
// Fields available in a struct or a map, but not in the Config object, will not
// be touched by Unpack unless they are initialized from InitDefaults. Those values will
// be validated using the same rules below just as if the values came from the configuration.
// This gives the requirement that pre-filled in values or defaults must also validate.
//
// Type aliases like "type myTypeAlias T" are unpacked using Unpack if the alias
// implements the Unpacker interface. Otherwise unpacking rules for type T will be used.
//
// When unpacking a value, the Validate method will be called if the value
// implements the Validator interface. Unpacking a struct field the validator
// options will be applied to the unpacked value as well.
//
// Struct field validators are set using the `validate` tag (configurable by
// ValidatorTag). Default validators options are:
//
// required: check value is set and not empty
// nonzero: check numeric value != 0 or string/slice not being empty
// positive: check numeric value >= 0
// min=<value>: check numeric value >= <value>. If target type is time.Duration,
// <value> can be a duration.
// max=<value>: check numeric value <= <value>. If target type is time.Duration,
// <value> can be a duration.
//
// If a config value is not the convertible to the target type, or overflows the
// target type, Unpack will abort immediately and return the appropriate error.
//
// If validator tags or validation provided by Validate or Unmarshal fails,
// Unpack will abort immediately and return the validate error.
//
// When unpacking into an interface{} value, Unpack will store a value of one of
// these types in the value:
//
// bool for boolean values
// int64 for signed integer values
// uint64 for unsigned integer values
// float64 for floating point values
// string for string values
// []interface{} for list-only Config objects
// map[string]interface{} for Config objects
// nil for pointers if key has a nil value
func (c *Config) Unpack(to interface{}, options ...Option) error {
opts := makeOptions(options)
if c == nil {
return raiseNil(ErrNilConfig)
}
if to == nil {
return raiseNil(ErrNilValue)
}
vTo := reflect.ValueOf(to)
k := vTo.Kind()
isValid := k == reflect.Ptr || k == reflect.Map
if !isValid {
return raisePointerRequired(vTo)
}
return reifyInto(opts, vTo, c)
}
// UnpackWithoutOptions method calls the Unpack method without any options provided.
func (c *Config) UnpackWithoutOptions(to interface{}) error {
return c.Unpack(to)
}
func reifyInto(opts *options, to reflect.Value, from *Config) Error {
to = chaseValuePointers(to)
if to, ok := tryTConfig(to); ok {
return mergeConfig(opts, to.Addr().Interface().(*Config), from)
}
tTo := chaseTypePointers(to.Type())
k := tTo.Kind()
switch k {
case reflect.Map:
return reifyMap(opts, to, from, nil)
case reflect.Struct:
return reifyStruct(opts, to, from)
case reflect.Slice, reflect.Array:
fopts := fieldOptions{opts: opts, tag: tagOptions{}, validators: nil}
v, err := reifyMergeValue(fopts, to, cfgSub{from})
if err != nil {
return err
}
to.Set(v)
return nil
}
return raiseInvalidTopLevelType(to.Interface(), opts.meta)
}
func reifyMap(opts *options, to reflect.Value, from *Config, validators []validatorTag) Error {
parentFields := opts.activeFields
defer func() { opts.activeFields = parentFields }()
if to.Type().Key().Kind() != reflect.String {
return raiseKeyInvalidTypeUnpack(to.Type(), from)
}
if to.IsNil() {
to.Set(reflect.MakeMap(to.Type()))
}
tryInitDefaults(to)
fields := from.fields.dict()
if len(fields) == 0 {
if err := tryRecursiveValidate(to, opts, validators); err != nil {
return raiseValidation(from.ctx, from.metadata, "", err)
}
return nil
}
for k, value := range fields {
opts.activeFields = newFieldSet(parentFields)
key := reflect.ValueOf(k)
old := to.MapIndex(key)
var v reflect.Value
var err Error
if !old.IsValid() {
v, err = reifyValue(fieldOptions{opts: opts}, to.Type().Elem(), value)
} else {
v, err = reifyMergeValue(fieldOptions{opts: opts}, old, value)
}
if err != nil {
return err
}
if v.IsValid() {
to.SetMapIndex(key, v)
}
}
if err := runValidators(to.Interface(), validators); err != nil {
return raiseValidation(from.ctx, from.metadata, "", err)
}
if err := tryValidate(to); err != nil {
return raiseValidation(from.ctx, from.metadata, "", err)
}
return nil
}
func reifyStruct(opts *options, orig reflect.Value, cfg *Config) Error {
parentFields := opts.activeFields
defer func() { opts.activeFields = parentFields }()
orig = chaseValuePointers(orig)
to := chaseValuePointers(reflect.New(chaseTypePointers(orig.Type())))
if orig.Kind() == reflect.Struct { // if orig is has been allocated copy into to
to.Set(orig)
}
if v, ok := valueIsUnpacker(to); ok {
err := unpackWith(opts, v, cfgSub{cfg})
if err != nil {
return err
}
} else {
tryInitDefaults(to)
numField := to.NumField()
for i := 0; i < numField; i++ {
fInfo, skip, err := accessField(to, i, opts)
if err != nil {
return err
}
if skip {
continue
}
if fInfo.tagOptions.squash {
vField := chaseValue(fInfo.value)
switch vField.Kind() {
case reflect.Struct, reflect.Map:
if err := reifyInto(fInfo.options, fInfo.value, cfg); err != nil {
return err
}
case reflect.Slice, reflect.Array:
fopts := fieldOptions{opts: fInfo.options, tag: fInfo.tagOptions, validators: fInfo.validatorTags}
v, err := reifyMergeValue(fopts, fInfo.value, cfgSub{cfg})
if err != nil {
return err
}
vField.Set(v)
default:
return raiseInlineNeedsObject(cfg, fInfo.name, fInfo.value.Type())
}
} else {
fopts := fieldOptions{opts: fInfo.options, tag: fInfo.tagOptions, validators: fInfo.validatorTags}
if err := reifyGetField(cfg, fopts, fInfo.name, fInfo.value, fInfo.ftype); err != nil {
return err
}
}
}
}
if err := tryValidate(to); err != nil {
return raiseValidation(cfg.ctx, cfg.metadata, "", err)
}
orig.Set(pointerize(orig.Type(), to.Type(), to))
return nil
}
func reifyGetField(
cfg *Config,
opts fieldOptions,
name string,
to reflect.Value,
fieldType reflect.Type,
) Error {
p := parsePathWithOpts(name, opts.opts)
value, err := p.GetValue(cfg, opts.opts)
if err != nil {
if err.Reason() != ErrMissing {
return err
}
value = nil
}
if isNil(value) {
// When fieldType is a pointer and the value is nil, return nil as the
// underlying type should not be allocated.
if fieldType.Kind() == reflect.Ptr {
if err := tryRecursiveValidate(to, opts.opts, opts.validators); err != nil {
return raiseValidation(cfg.ctx, cfg.metadata, name, err)
}
return nil
}
// Primitive types return early when it doesn't implement the Initializer interface.
if fieldType.Kind() != reflect.Struct && !hasInitDefaults(fieldType) {
if err := tryRecursiveValidate(to, opts.opts, opts.validators); err != nil {
return raiseValidation(cfg.ctx, cfg.metadata, name, err)
}
return nil
}
// None primitive types always get initialized even if it doesn't implement the
// Initializer interface, because nested types might implement the Initializer interface.
if value == nil {
value = &cfgNil{cfgPrimitive{cfg.ctx, cfg.metadata}}
}
}
v, err := reifyMergeValue(opts, to, value)
if err != nil {
return err
}
if v.IsValid() {
to.Set(pointerize(to.Type(), v.Type(), v))
}
return nil
}
func reifyValue(
opts fieldOptions,
t reflect.Type,
val value,
) (reflect.Value, Error) {
if t.Kind() == reflect.Interface && t.NumMethod() == 0 {
reified, err := val.reify(opts.opts)
if err != nil {
ctx := val.Context()
return reflect.Value{}, raisePathErr(err, val.meta(), "", ctx.path("."))
}
return reflect.ValueOf(reified), nil
}
baseType := chaseTypePointers(t)
if tConfig.ConvertibleTo(baseType) {
cfg, err := val.toConfig(opts.opts)
if err != nil {
return reflect.Value{}, raiseExpectedObject(opts.opts, val)
}
v := reflect.ValueOf(cfg).Convert(reflect.PtrTo(baseType))
if t == baseType { // copy config
v = v.Elem()
} else {
v = pointerize(t, baseType, v)
}
return v, nil
}
if baseType.Kind() == reflect.Struct {
sub, err := val.toConfig(opts.opts)
if err != nil {
return reifyPrimitive(opts, val, t, baseType)
}
newSt := reflect.New(baseType)
if err := reifyInto(opts.opts, newSt, sub); err != nil {
return reflect.Value{}, err
}
if t.Kind() != reflect.Ptr {
return newSt.Elem(), nil
}
return pointerize(t, baseType, newSt), nil
}
switch baseType.Kind() {
case reflect.Map:
sub, err := val.toConfig(opts.opts)
if err != nil {
return reflect.Value{}, raiseExpectedObject(opts.opts, val)
}
if baseType.Key().Kind() != reflect.String {
return reflect.Value{}, raiseKeyInvalidTypeUnpack(baseType, sub)
}
newMap := reflect.MakeMap(baseType)
if err := reifyInto(opts.opts, newMap, sub); err != nil {
return reflect.Value{}, err
}
return newMap, nil
case reflect.Slice:
v, err := reifySlice(opts, baseType, val)
if err != nil {
return reflect.Value{}, err
}
return pointerize(t, baseType, v), nil
}
return reifyPrimitive(opts, val, t, baseType)
}
func reifyMergeValue(
opts fieldOptions,
oldValue reflect.Value, val value,
) (reflect.Value, Error) {
old := chaseValueInterfaces(oldValue)
t := old.Type()
old = chaseValuePointers(old)
if (old.Kind() == reflect.Ptr || old.Kind() == reflect.Interface) && old.IsNil() {
return reifyValue(opts, t, val)
}
baseType := chaseTypePointers(old.Type())
if tConfig.ConvertibleTo(baseType) {
sub, err := val.toConfig(opts.opts)
if err != nil {
return reflect.Value{}, raiseExpectedObject(opts.opts, val)
}
if t == baseType {
// no pointer -> return type mismatch
return reflect.Value{}, raisePointerRequired(oldValue)
}
// check if old is nil -> copy reference only
if old.Kind() == reflect.Ptr && old.IsNil() {
v, err := val.reflect(opts.opts)
if err != nil {
ctx := val.Context()
return reflect.Value{}, raisePathErr(err, val.meta(), "", ctx.path("."))
}
v = v.Convert(reflect.PtrTo(baseType))
return pointerize(t, baseType, v), nil
}
// check if old == value
subOld := chaseValuePointers(old).Addr().Convert(tConfigPtr).Interface().(*Config)
if sub == subOld {
return oldValue, nil
}
// old != value -> merge value into old
return oldValue, mergeFieldConfig(opts, subOld, sub)
}
if v, ok := valueIsUnpacker(old); ok {
err := unpackWith(opts.opts, v, val)
if err != nil {
return reflect.Value{}, err
}
return old, nil
}
switch baseType.Kind() {
case reflect.Map:
sub, err := val.toConfig(opts.opts)
if err != nil {
return reflect.Value{}, raiseExpectedObject(opts.opts, val)
}
return old, reifyMap(opts.opts, old, sub, opts.validators)
case reflect.Struct:
sub, err := val.toConfig(opts.opts)
if err != nil {
return reflect.Value{}, raiseExpectedObject(opts.opts, val)
}
return oldValue, reifyStruct(opts.opts, old, sub)
case reflect.Array:
return reifyArray(opts, old, baseType, val)
case reflect.Slice:
return reifySliceMerge(opts, old, baseType, val)
}
return reifyPrimitive(opts, val, t, baseType)
}
func mergeFieldConfig(opts fieldOptions, to, from *Config) Error {
return mergeConfig(opts.opts, to, from)
}
func reifyArray(
opts fieldOptions,
to reflect.Value, tTo reflect.Type,
val value,
) (reflect.Value, Error) {
arr, err := castArr(opts.opts, val)
if err != nil {
return reflect.Value{}, err
}
if len(arr) != tTo.Len() {
ctx := val.Context()
return reflect.Value{}, raiseArraySize(ctx, val.meta(), len(arr), tTo.Len())
}
return reifyDoArray(opts, to, tTo.Elem(), 0, val, arr)
}
func reifySlice(
opts fieldOptions,
tTo reflect.Type,
val value,
) (reflect.Value, Error) {
return reifySliceMerge(opts, reflect.Value{}, tTo, val)
}
func reifySliceMerge(
opts fieldOptions,
old reflect.Value,
tTo reflect.Type,
val value,
) (reflect.Value, Error) {
arr, err := castArr(opts.opts, val)
if err != nil {
return reflect.Value{}, err
}
arrMergeCfg := opts.configHandling()
l := len(arr)
start := 0
cpyStart := 0
withOld := old.IsValid() && !old.IsNil()
if withOld {
ol := old.Len()
switch arrMergeCfg {
case cfgReplaceValue:
// do nothing
case cfgArrAppend:
l += ol
start = ol
case cfgArrPrepend:
cpyStart = l
l += ol
default:
if l < ol {
l = ol
}
}
}
tmp := reflect.MakeSlice(tTo, l, l)
if withOld {
reflect.Copy(tmp.Slice(cpyStart, tmp.Len()), old)
}
return reifyDoArray(opts, tmp, tTo.Elem(), start, val, arr)
}
func reifyDoArray(
opts fieldOptions,
to reflect.Value, elemT reflect.Type,
start int,
val value,
arr []value,
) (reflect.Value, Error) {
aLen := len(arr)
tLen := to.Len()
for idx := 0; idx < tLen; idx++ {
if idx >= start && idx < start+aLen {
v, err := reifyMergeValue(opts, to.Index(idx), arr[idx-start])
if err != nil {
return reflect.Value{}, err
}
if v.IsValid() {
to.Index(idx).Set(v)
}
} else {
if err := tryRecursiveValidate(to.Index(idx), opts.opts, nil); err != nil {
return reflect.Value{}, raiseValidation(val.Context(), val.meta(), "", err)
}
}
}
if err := runValidators(to.Interface(), opts.validators); err != nil {
ctx := val.Context()
return reflect.Value{}, raiseValidation(ctx, val.meta(), "", err)
}
if err := tryValidate(to); err != nil {
ctx := val.Context()
return reflect.Value{}, raiseValidation(ctx, val.meta(), "", err)
}
return to, nil
}
func castArr(opts *options, v value) ([]value, Error) {
if sub, ok := v.(cfgSub); ok {
return sub.c.fields.array(), nil
}
if ref, ok := v.(*cfgDynamic); ok {
unrefed, err := ref.getValue(opts)
if err != nil {
return nil, raiseMissingMsg(ref.ctx.getParent(), ref.ctx.field, err.Error())
}
if sub, ok := unrefed.(cfgSub); ok {
return sub.c.fields.array(), nil
}
}
l, err := v.Len(opts)
if err != nil {
ctx := v.Context()
return nil, raisePathErr(err, v.meta(), "", ctx.path("."))
}
if l == 0 {
return nil, nil
}
return []value{v}, nil
}
func reifyPrimitive(
opts fieldOptions,
val value,
t, baseType reflect.Type,
) (reflect.Value, Error) {
// zero initialize value if val==nil
if isNil(val) {
v := pointerize(t, baseType, reflect.Zero(baseType))
return tryInitDefaults(v), nil
}
var v reflect.Value
var err Error
var ok bool
if v, ok = typeIsUnpacker(baseType); ok {
err := unpackWith(opts.opts, v, val)
if err != nil {
return reflect.Value{}, err
}
} else {
v, err = doReifyPrimitive(opts, val, baseType)
if err != nil {
return v, err
}
}
if err := runValidators(v.Interface(), opts.validators); err != nil {
return reflect.Value{}, raiseValidation(val.Context(), val.meta(), "", err)
}
if err := tryValidate(v); err != nil {
return reflect.Value{}, raiseValidation(val.Context(), val.meta(), "", err)
}
return pointerize(t, baseType, chaseValuePointers(v)), nil
}
func doReifyPrimitive(
opts fieldOptions,
val value,
baseType reflect.Type,
) (reflect.Value, Error) {
extras := map[reflect.Type]func(fieldOptions, value, reflect.Type) (reflect.Value, Error){
tDuration: reifyDuration,
tRegexp: reifyRegexp,
}
previous := opts.opts.activeFields
opts.opts.activeFields = newFieldSet(previous)
valT, err := val.typ(opts.opts)
if err != nil {
ctx := val.Context()
return reflect.Value{}, raisePathErr(err, val.meta(), "", ctx.path("."))
}
opts.opts.activeFields = previous
// try primitive conversion
kind := baseType.Kind()
switch {
case valT.gotype == baseType:
v, err := val.reflect(opts.opts)
if err != nil {
ctx := val.Context()
return reflect.Value{}, raisePathErr(err, val.meta(), "", ctx.path("."))
}
return v, nil
case kind == reflect.String:
s, err := val.toString(opts.opts)
if err != nil {
return reflect.Value{}, raiseConversion(opts.opts, val, err, "string")
}
return reflect.ValueOf(s), nil
case extras[baseType] != nil:
v, err := extras[baseType](opts, val, baseType)
if err != nil {
return v, err
}
return v, nil
case isInt(kind):
v, err := reifyInt(opts, val, baseType)
if err != nil {
return v, err
}
return v, nil
case isUint(kind):
v, err := reifyUint(opts, val, baseType)
if err != nil {
return v, err
}
return v, nil
case isFloat(kind):
v, err := reifyFloat(opts, val, baseType)
if err != nil {
return v, err
}
return v, nil
case kind == reflect.Bool:
v, err := reifyBool(opts, val, baseType)
if err != nil {
return v, err
}
return v, nil
case valT.gotype.ConvertibleTo(baseType):
v, err := val.reflect(opts.opts)
if err != nil {
ctx := val.Context()
return reflect.Value{}, raisePathErr(err, val.meta(), "", ctx.path("."))
}
return v.Convert(baseType), nil
}
return reflect.Value{}, raiseToTypeNotSupported(opts.opts, val, baseType)
}
func reifyDuration(
opts fieldOptions,
val value,
_ reflect.Type,
) (reflect.Value, Error) {
var d time.Duration
var err error
switch v := val.(type) {
case *cfgInt:
d = time.Duration(v.i) * time.Second
case *cfgUint:
d = time.Duration(v.u) * time.Second
case *cfgFloat:
d = time.Duration(v.f * float64(time.Second))
case *cfgString:
d, err = time.ParseDuration(v.s)
default:
var s string
s, err = val.toString(opts.opts)
if err != nil {
return reflect.Value{}, raiseInvalidDuration(val, err)
}
d, err = time.ParseDuration(s)
}
if err != nil {
return reflect.Value{}, raiseInvalidDuration(val, err)
}
return reflect.ValueOf(d), nil
}
func reifyRegexp(
opts fieldOptions,
val value,
_ reflect.Type,
) (reflect.Value, Error) {
s, err := val.toString(opts.opts)
if err != nil {
return reflect.Value{}, raiseConversion(opts.opts, val, err, "regex")
}
r, err := regexp.Compile(s)
if err != nil {
return reflect.Value{}, raiseInvalidRegexp(val, err)
}
return reflect.ValueOf(r).Elem(), nil
}
func reifyInt(
opts fieldOptions,
val value,
t reflect.Type,
) (reflect.Value, Error) {
i, err := val.toInt(opts.opts)
if err != nil {
return reflect.Value{}, raiseConversion(opts.opts, val, err, "int")
}
tmp := reflect.Zero(t)
if tmp.OverflowInt(i) {
return reflect.Value{}, raiseConversion(opts.opts, val, ErrOverflow, "int")
}
return reflect.ValueOf(i).Convert(t), nil
}
func reifyUint(
opts fieldOptions,
val value,
t reflect.Type,
) (reflect.Value, Error) {
u, err := val.toUint(opts.opts)
if err != nil {
return reflect.Value{}, raiseConversion(opts.opts, val, err, "uint")
}
tmp := reflect.Zero(t)
if tmp.OverflowUint(u) {
return reflect.Value{}, raiseConversion(opts.opts, val, ErrOverflow, "uint")
}
return reflect.ValueOf(u).Convert(t), nil
}
func reifyFloat(
opts fieldOptions,
val value,
t reflect.Type,
) (reflect.Value, Error) {
f, err := val.toFloat(opts.opts)
if err != nil {
return reflect.Value{}, raiseConversion(opts.opts, val, err, "float")
}
tmp := reflect.Zero(t)
if tmp.OverflowFloat(f) {
return reflect.Value{}, raiseConversion(opts.opts, val, ErrOverflow, "float")
}
return reflect.ValueOf(f).Convert(t), nil
}
func reifyBool(
opts fieldOptions,
val value,
t reflect.Type,
) (reflect.Value, Error) {
b, err := val.toBool(opts.opts)
if err != nil {
return reflect.Value{}, raiseConversion(opts.opts, val, err, "bool")
}
return reflect.ValueOf(b).Convert(t), nil
}