k3s/vendor/github.com/rancher/spur/generic/generic.go

242 lines
6.2 KiB
Go

// Copyright 2020 Rancher Labs, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package generic
import (
"encoding/json"
"fmt"
"reflect"
"time"
"gopkg.in/yaml.v2"
)
// Marshal is the function used for marshaling slices
var Marshal = json.Marshal
// Unmarshal is the function used for un-marshaling slices
var Unmarshal = yaml.Unmarshal
// ToStringFunc is the function definition for converting types to strings
type ToStringFunc = func(interface{}) (string, bool)
// FromStringFunc is the function definition for converting strings to types
type FromStringFunc = func(string) (interface{}, error)
// ToStringMap provides a mapping of type to string conversion function
var ToStringMap = map[string]ToStringFunc{}
// FromStringMap provides a mapping of string to type conversion function
var FromStringMap = map[string]FromStringFunc{}
// TimeLayouts provides a list of layouts to attempt when converting time strings
var TimeLayouts = []string{
time.RFC3339Nano,
time.RFC3339,
time.UnixDate,
time.RubyDate,
time.ANSIC,
time.RFC822,
time.RFC822Z,
time.RFC850,
time.RFC1123,
time.RFC1123Z,
time.StampNano,
time.StampMicro,
time.StampMilli,
time.Stamp,
time.Kitchen,
}
// ToString is a convenience function for converting types to strings as defined in ToStringMap
func ToString(value interface{}) (string, bool) {
if value == nil {
return "", false
}
if toString := ToStringMap[TypeOf(value).String()]; toString != nil {
return toString(value)
}
return "", false
}
// FromString is a convenience function for converting strings to types as defined in FromStringMap
func FromString(value string, ptr interface{}) error {
PtrPanic(ptr)
typ := reflect.TypeOf(ptr).Elem().String()
fromString := FromStringMap[typ]
if fromString == nil {
return errParse
}
val, err := fromString(value)
if err != nil {
return numError(err)
}
Set(ptr, val)
return nil
}
// TypeOf returns the dereferenced value's type
func TypeOf(value interface{}) reflect.Type {
typ := reflect.TypeOf(value)
if typ != nil && typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
return typ
}
// ElemTypeOf returns the dereferenced value's type or TypeOf is not an Elem
func ElemTypeOf(value interface{}) reflect.Type {
typ := TypeOf(value)
if typ.Kind() == reflect.Slice {
return typ.Elem()
}
return typ
}
// New returns a new reflection with TypeOf value
func New(value interface{}) interface{} {
return reflect.New(TypeOf(value)).Interface()
}
// NewElem returns a new reflection with ElemTypeOf value
func NewElem(value interface{}) interface{} {
return reflect.New(ElemTypeOf(value)).Interface()
}
// Zero returns a zero reflection with TypeOf value
func Zero(value interface{}) interface{} {
return reflect.Zero(TypeOf(value)).Interface()
}
// IsSlice return true if the TypeOf value is a slice
func IsSlice(value interface{}) bool {
if value == nil {
return false
}
return TypeOf(value).Kind() == reflect.Slice
}
// PtrPanic halts execution if the passed ptr is not a pointer
func PtrPanic(ptr interface{}) {
if !IsPtr(ptr) {
panic(fmt.Errorf("expected pointer type, got %s", reflect.TypeOf(ptr).String()))
}
}
// Set will assign the contents of ptr to value
func Set(ptr interface{}, value interface{}) {
PtrPanic(ptr)
if value == nil {
return
}
reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(value))
}
// Len returns the length of a slice, or -1 if not a slice
func Len(value interface{}) int {
if !IsSlice(value) {
return -1
}
return reflect.ValueOf(value).Len()
}
// Index will return the value of a slice at a given index
func Index(value interface{}, i int) interface{} {
if !IsSlice(value) {
return nil
}
return reflect.ValueOf(value).Index(i).Interface()
}
// Append will append an element onto a generic slice
func Append(slice interface{}, elem interface{}) interface{} {
return reflect.Append(reflect.ValueOf(slice), reflect.ValueOf(elem)).Interface()
}
// IsPtr returns true if the given value is of kind reflect.Ptr
func IsPtr(value interface{}) bool {
if value == nil {
return false
}
return reflect.TypeOf(value).Kind() == reflect.Ptr
}
// ValueOfPtr returns the contents of a pointer, or the given value if not a pointer
func ValueOfPtr(value interface{}) interface{} {
if !IsPtr(value) {
return value
}
elem := reflect.ValueOf(value).Elem()
if !elem.IsValid() {
return nil
}
return elem.Interface()
}
// Convert will return a new result of type src, where value is converted to the type
// of src or appended if src is a slice and value is an element
func Convert(src interface{}, value interface{}) (interface{}, error) {
// Convert an element
elem, err := ConvertElem(src, value)
if !IsSlice(src) {
// Return value and error if not a slice
return elem, err
}
// Try deserializing as string
if s, ok := value.(string); ok {
val := New(src)
if err := Unmarshal([]byte(s), val); err == nil {
return ValueOfPtr(val), nil
}
}
// If no error from converting element return appended value
if err == nil {
return Append(src, elem), nil
}
// Try evaluating value as a slice of interfaces
otherValue, ok := value.([]interface{})
if !ok {
return nil, errParse
}
// Create a new slice and append each converted element
slice := Zero(src)
for _, other := range otherValue {
elem, err := ConvertElem(src, other)
if err != nil {
return nil, err
}
slice = Append(slice, elem)
}
return slice, nil
}
// ConvertElem will return a new result, where value is converted to the type
// of src or returned as an element if src is a slice
func ConvertElem(src interface{}, value interface{}) (interface{}, error) {
// Get our value as a string
s, ok := value.(string)
if !ok {
if s, ok = ToString(value); !ok {
return nil, errParse
}
}
// Return a new value from the string
ptr := NewElem(src)
err := FromString(s, ptr)
return ValueOfPtr(ptr), err
}
// Stringify returns the ToString version of the value, or the Marshaled version
// in the case of slices, otherwise panic if cannot be converted to string
func Stringify(value interface{}) string {
if s, ok := ToString(value); ok {
return s
}
if b, err := Marshal(value); err == nil {
return string(b)
}
panic(errParse)
}