SandpointsGitHook/vendor/github.com/bep/tmc/codec.go

309 lines
7.9 KiB
Go

// Copyright © 2019 Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package tmc
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"
)
// JSONMarshaler encodes and decodes JSON and is the default used in this
// codec.
var JSONMarshaler = new(jsonMarshaler)
// New creates a new Coded with some optional options.
func New(opts ...Option) (*Codec, error) {
c := &Codec{
typeSep: "|",
marshaler: JSONMarshaler,
typeAdapters: DefaultTypeAdapters,
typeAdaptersMap: make(map[reflect.Type]Adapter),
typeAdaptersStringMap: make(map[string]Adapter),
}
for _, opt := range opts {
if err := opt(c); err != nil {
return c, err
}
}
for _, w := range c.typeAdapters {
tp := w.Type()
c.typeAdaptersMap[tp] = w
c.typeAdaptersStringMap[tp.String()] = w
}
return c, nil
}
// Option configures the Codec.
type Option func(c *Codec) error
// WithTypeSep sets the separator to use before the type information encoded in
// the key field. Default is "|".
func WithTypeSep(sep string) func(c *Codec) error {
return func(c *Codec) error {
if sep == "" {
return errors.New("separator cannot be empty")
}
c.typeSep = sep
return nil
}
}
// WithMarshalUnmarshaler sets the MarshalUnmarshaler to use.
// Default is JSONMarshaler.
func WithMarshalUnmarshaler(marshaler MarshalUnmarshaler) func(c *Codec) error {
return func(c *Codec) error {
c.marshaler = marshaler
return nil
}
}
// WithTypeAdapters sets the type adapters to use. Note that if more than one
// adapter exists for the same type, the last one will win. This means that
// if you want to use the default adapters, but override some of them, you
// can do:
//
// adapters := append(typedmapcodec.DefaultTypeAdapters, mycustomAdapters ...)
// codec := typedmapcodec.New(WithTypeAdapters(adapters))
//
func WithTypeAdapters(typeAdapters []Adapter) func(c *Codec) error {
return func(c *Codec) error {
c.typeAdapters = typeAdapters
return nil
}
}
// Codec provides methods to marshal and unmarshal a Go map while preserving
// type information.
type Codec struct {
typeSep string
marshaler MarshalUnmarshaler
typeAdapters []Adapter
typeAdaptersMap map[reflect.Type]Adapter
typeAdaptersStringMap map[string]Adapter
}
// Marshal accepts a Go map and marshals it to the configured marshaler
// anntated with type information.
func (c *Codec) Marshal(v interface{}) ([]byte, error) {
m, err := c.toTypedMap(v)
if err != nil {
return nil, err
}
return c.marshaler.Marshal(m)
}
// Unmarshal unmarshals the given data to the given Go map, using
// any annotated type information found to preserve the type information
// stored in Marshal.
func (c *Codec) Unmarshal(data []byte, v interface{}) error {
if err := c.marshaler.Unmarshal(data, v); err != nil {
return err
}
_, err := c.fromTypedMap(v)
return err
}
func (c *Codec) newKey(key reflect.Value, a Adapter) reflect.Value {
return reflect.ValueOf(fmt.Sprintf("%s%s%s", key, c.typeSep, a.Type()))
}
func (c *Codec) fromTypedMap(mi interface{}) (reflect.Value, error) {
m := reflect.ValueOf(mi)
if m.Kind() == reflect.Ptr {
m = m.Elem()
}
if m.Kind() != reflect.Map {
return reflect.Value{}, errors.New("must be a Map")
}
keyKind := m.Type().Key().Kind()
if keyKind == reflect.Interface {
// We only support string keys.
// YAML creates map[interface {}]interface {}, so try to convert it.
var err error
m, err = c.toStringMap(m)
if err != nil {
return reflect.Value{}, err
}
}
for _, key := range m.MapKeys() {
v := indirectInterface(m.MapIndex(key))
var (
keyStr = key.String()
keyPlain string
keyType string
)
sepIdx := strings.LastIndex(keyStr, c.typeSep)
if sepIdx != -1 {
keyPlain = keyStr[:sepIdx]
keyType = keyStr[sepIdx+len(c.typeSep):]
}
adapter, found := c.typeAdaptersStringMap[keyType]
if !found {
if v.Kind() == reflect.Map {
var err error
v, err = c.fromTypedMap(v.Interface())
if err != nil {
return reflect.Value{}, err
}
m.SetMapIndex(key, v)
}
continue
}
switch v.Kind() {
case reflect.Map:
mm := reflect.MakeMap(reflect.MapOf(stringType, adapter.Type()))
for _, key := range v.MapKeys() {
vv := indirectInterface(v.MapIndex(key))
nv, err := adapter.FromString(vv.String())
if err != nil {
return reflect.Value{}, err
}
mm.SetMapIndex(indirectInterface(key), reflect.ValueOf(nv))
}
m.SetMapIndex(reflect.ValueOf(keyPlain), mm)
case reflect.Slice:
slice := reflect.MakeSlice(reflect.SliceOf(adapter.Type()), v.Len(), v.Cap())
for i := 0; i < v.Len(); i++ {
vv := indirectInterface(v.Index(i))
nv, err := adapter.FromString(vv.String())
if err != nil {
return reflect.Value{}, err
}
slice.Index(i).Set(reflect.ValueOf(nv))
}
m.SetMapIndex(reflect.ValueOf(keyPlain), slice)
default:
nv, err := adapter.FromString(v.String())
if err != nil {
return reflect.Value{}, err
}
m.SetMapIndex(reflect.ValueOf(keyPlain), reflect.ValueOf(nv))
}
m.SetMapIndex(key, reflect.Value{})
}
return m, nil
}
var (
interfaceMapType = reflect.TypeOf(make(map[string]interface{}))
interfaceSliceType = reflect.TypeOf([]interface{}{})
stringType = reflect.TypeOf("")
)
func (c *Codec) toTypedMap(mi interface{}) (interface{}, error) {
mv := reflect.ValueOf(mi)
if mv.Kind() != reflect.Map || mv.Type().Key().Kind() != reflect.String {
return nil, errors.New("must provide a map with string keys")
}
m := reflect.MakeMap(interfaceMapType)
for _, key := range mv.MapKeys() {
v := indirectInterface(mv.MapIndex(key))
switch v.Kind() {
case reflect.Map:
if wrapper, found := c.typeAdaptersMap[v.Type().Elem()]; found {
mm := reflect.MakeMap(interfaceMapType)
for _, key := range v.MapKeys() {
mm.SetMapIndex(key, reflect.ValueOf(wrapper.Wrap(v.MapIndex(key).Interface())))
}
m.SetMapIndex(c.newKey(key, wrapper), mm)
} else {
nested, err := c.toTypedMap(v.Interface())
if err != nil {
return nil, err
}
m.SetMapIndex(key, reflect.ValueOf(nested))
}
continue
case reflect.Slice:
if adapter, found := c.typeAdaptersMap[v.Type().Elem()]; found {
slice := reflect.MakeSlice(interfaceSliceType, v.Len(), v.Cap())
for i := 0; i < v.Len(); i++ {
slice.Index(i).Set(reflect.ValueOf(adapter.Wrap(v.Index(i).Interface())))
}
m.SetMapIndex(c.newKey(key, adapter), slice)
continue
}
}
if adapter, found := c.typeAdaptersMap[v.Type()]; found {
m.SetMapIndex(c.newKey(key, adapter), reflect.ValueOf(adapter.Wrap(v.Interface())))
} else {
m.SetMapIndex(key, v)
}
}
return m.Interface(), nil
}
func (c *Codec) toStringMap(mi reflect.Value) (reflect.Value, error) {
elemType := mi.Type().Elem()
m := reflect.MakeMap(reflect.MapOf(stringType, elemType))
for _, key := range mi.MapKeys() {
key = indirectInterface(key)
if key.Kind() != reflect.String {
return reflect.Value{}, errors.New("this library supports only string keys in maps")
}
vv := mi.MapIndex(key)
m.SetMapIndex(reflect.ValueOf(key.String()), vv)
}
return m, nil
}
// MarshalUnmarshaler is the interface that must be implemented if you want to
// add support for more than JSON to this codec.
type MarshalUnmarshaler interface {
Marshal(v interface{}) ([]byte, error)
Unmarshal(b []byte, v interface{}) error
}
type jsonMarshaler int
func (jsonMarshaler) Marshal(v interface{}) ([]byte, error) {
return json.Marshal(v)
}
func (jsonMarshaler) Unmarshal(b []byte, v interface{}) error {
return json.Unmarshal(b, v)
}
// Based on: https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L931
func indirectInterface(v reflect.Value) reflect.Value {
if v.Kind() != reflect.Interface {
return v
}
if v.IsNil() {
return reflect.Value{}
}
return v.Elem()
}