mc root source folder cleanup

This commit is contained in:
adman
2026-06-27 13:54:54 +02:00
parent b2784574bd
commit 666f250b3e
480 changed files with 325 additions and 325 deletions
+208
View File
@@ -0,0 +1,208 @@
package mapstructure
import (
"fmt"
"reflect"
"github.com/admin-else/strom/mc/util"
)
func (f *Format) decode(m map[string]any, target reflect.Value) error {
t := target.Type()
usedKeys := map[string]bool{}
for i := range t.NumField() {
field := t.Field(i)
if !field.IsExported() {
continue
}
tag := parseTag(field.Tag.Get(f.Name))
if tag.Skip {
continue
}
key := tag.Name
if key == "" {
key = field.Name
}
val, exists := m[key]
if !exists && f.trySnakeCase {
val, exists = m[util.SnakeCase(key)]
}
if !exists && f.tryLowCase {
val, exists = m[util.FirstLetterLower(key)]
}
if !exists {
if tag.Required || (f.requireAll && !tag.OmitEmpty) {
return fmt.Errorf("%w: %s", ErrMissingRequired, key)
}
continue
}
usedKeys[key] = true
fieldVal := target.Field(i)
if err := f.setField(fieldVal, val, tag); err != nil {
return fmt.Errorf("field %s: %w", key, err)
}
}
if f.errorOnExtra {
for key := range m {
if !usedKeys[key] {
return fmt.Errorf("%w: %s", ErrExtraKey, key)
}
}
}
return nil
}
func (f *Format) setField(target reflect.Value, value any, opts tagOptions) error {
if value == nil {
if opts.OmitEmpty {
return nil
}
target.Set(reflect.Zero(target.Type()))
return nil
}
targetType := target.Type()
if codec, ok := f.decoders[targetType]; ok {
result, err := codec(value, opts.Raw)
if err != nil {
return err
}
target.Set(reflect.ValueOf(result))
return nil
}
src := reflect.ValueOf(value)
if src.Type().AssignableTo(targetType) {
target.Set(src)
return nil
}
if src.Type().ConvertibleTo(targetType) {
target.Set(src.Convert(targetType))
return nil
}
if targetType.Kind() == reflect.Struct {
nested, ok := value.(map[string]any)
if !ok {
return fmt.Errorf("cannot map %T to struct %s", value, targetType.Name())
}
nestedTarget := reflect.New(targetType).Elem()
if err := f.decode(nested, nestedTarget); err != nil {
return err
}
target.Set(nestedTarget)
return nil
}
if targetType.Kind() == reflect.Slice {
return f.setSlice(target, value)
}
if targetType.Kind() == reflect.Map {
return f.setMap(target, value)
}
if targetType.Kind() == reflect.Bool {
return f.setBool(target, value)
}
// this fixes this edge case
// level_test.go:19: field Data: field Player: field Pos: cannot set [3]float64 from []interface {}
if targetType.Kind() == reflect.Array && src.Kind() == reflect.Slice {
if target.Len() != src.Len() {
return fmt.Errorf("cannot set %s from %T because lengths do not match", targetType, value)
}
for i := 0; i < src.Len(); i++ {
if src.Index(i).Type().AssignableTo(targetType.Elem()) {
target.Index(i).Set(src.Index(i))
continue
}
if src.Index(i).Elem().Type().AssignableTo(targetType.Elem()) {
target.Index(i).Set(src.Index(i).Elem())
continue
}
return fmt.Errorf("cannot set %s from %T because element %d cannot be converted to %s", targetType, value, i, targetType.Elem())
}
return nil
}
return fmt.Errorf("cannot set %s from %T", targetType, value)
}
func (f *Format) setBool(target reflect.Value, value any) error {
switch v := value.(type) {
case int8:
target.SetBool(v != 0)
case int16:
target.SetBool(v != 0)
case int32:
target.SetBool(v != 0)
case int64:
target.SetBool(v != 0)
default:
return fmt.Errorf("cannot convert %T to bool", value)
}
return nil
}
func (f *Format) setSlice(target reflect.Value, value any) error {
src, ok := value.([]any)
if !ok {
return fmt.Errorf("cannot map %T to slice", value)
}
elemType := target.Type().Elem()
slice := reflect.MakeSlice(target.Type(), 0, len(src))
for _, item := range src {
elem := reflect.New(elemType).Elem()
if err := f.setField(elem, item, tagOptions{}); err != nil {
return err
}
slice = reflect.Append(slice, elem)
}
target.Set(slice)
return nil
}
func (f *Format) setMap(target reflect.Value, value any) error {
src, ok := value.(map[string]any)
if !ok {
return fmt.Errorf("cannot map %T to map", value)
}
mapType := target.Type()
keyType := mapType.Key()
elemType := mapType.Elem()
result := reflect.MakeMap(mapType)
for k, v := range src {
keyVal := reflect.ValueOf(k)
if !keyVal.Type().AssignableTo(keyType) {
keyVal = keyVal.Convert(keyType)
}
elemVal := reflect.New(elemType).Elem()
if err := f.setField(elemVal, v, tagOptions{}); err != nil {
return err
}
result.SetMapIndex(keyVal, elemVal)
}
target.Set(result)
return nil
}
+7
View File
@@ -0,0 +1,7 @@
package mapstructure
var DefaultFormat = NewFormat("mapstructure", WithRequireAll(), WithTrySnakeCase(), WithTryLowCase())
func Decode(data any, v any) error {
return nil
}
+92
View File
@@ -0,0 +1,92 @@
package mapstructure
import (
"fmt"
"reflect"
)
func (f *Format) encode(v reflect.Value) (map[string]any, error) {
t := v.Type()
result := map[string]any{}
for i := range t.NumField() {
field := t.Field(i)
if !field.IsExported() {
continue
}
tag := parseTag(field.Tag.Get(f.Name))
if tag.Skip {
continue
}
key := tag.Name
if key == "" {
key = field.Name
}
fieldVal := v.Field(i)
if tag.OmitEmpty && fieldVal.IsZero() {
continue
}
encoded, err := f.encodeField(fieldVal, tag.Raw)
if err != nil {
return nil, fmt.Errorf("field %s: %w", key, err)
}
result[key] = encoded
}
return result, nil
}
func (f *Format) encodeField(v reflect.Value, tagStr string) (any, error) {
if codec, ok := f.encoders[v.Type()]; ok {
return codec(v.Interface(), tagStr)
}
switch v.Kind() {
case reflect.Struct:
return f.encode(v)
case reflect.Slice:
if v.IsNil() {
return []any{}, nil
}
slice := make([]any, v.Len())
for i := range v.Len() {
encoded, err := f.encodeField(v.Index(i), tagStr)
if err != nil {
return nil, err
}
slice[i] = encoded
}
return slice, nil
case reflect.Map:
if v.IsNil() {
return map[string]any{}, nil
}
m := make(map[string]any, v.Len())
iter := v.MapRange()
for iter.Next() {
key := iter.Key().String()
encoded, err := f.encodeField(iter.Value(), tagStr)
if err != nil {
return nil, err
}
m[key] = encoded
}
return m, nil
case reflect.Pointer:
if v.IsNil() {
return nil, nil
}
return f.encodeField(v.Elem(), tagStr)
case reflect.Bool:
if v.Bool() {
return int8(1), nil
}
return int8(0), nil
default:
return v.Interface(), nil
}
}
+125
View File
@@ -0,0 +1,125 @@
package mapstructure
import (
"errors"
"reflect"
)
var (
ErrNotPointer = errors.New("target must be a pointer")
ErrNilPointer = errors.New("target must not be a nil pointer")
ErrNotStruct = errors.New("target must be a pointer to a struct")
ErrNotMap = errors.New("data must be a map[string]any")
ErrMissingRequired = errors.New("missing required field")
ErrExtraKey = errors.New("map contains key with no matching struct field")
)
type Format struct {
Name string
requireAll bool
errorOnExtra bool
trySnakeCase bool
tryLowCase bool
decoders map[reflect.Type]func(any, string) (any, error)
encoders map[reflect.Type]func(any, string) (any, error)
}
func NewFormat(name string, opts ...Option) *Format {
f := &Format{
Name: name,
decoders: make(map[reflect.Type]func(any, string) (any, error)),
encoders: make(map[reflect.Type]func(any, string) (any, error)),
}
for _, opt := range opts {
opt(f)
}
return f
}
type Option func(*Format)
func WithRequireAll() Option {
return func(f *Format) {
f.requireAll = true
}
}
func WithErrorOnExtra() Option {
return func(f *Format) {
f.errorOnExtra = true
}
}
func WithTrySnakeCase() Option {
return func(f *Format) {
f.trySnakeCase = true
}
}
func WithTryLowCase() Option {
return func(f *Format) {
f.tryLowCase = true
}
}
func WithTypeCodec(decodeFn, encodeFn any) Option {
dt := reflect.TypeOf(decodeFn)
et := reflect.TypeOf(encodeFn)
if dt.Kind() != reflect.Func || dt.NumIn() != 2 || dt.NumOut() != 2 {
panic("decode function must have signature func(any, string) (T, error)")
}
if et.Kind() != reflect.Func || et.NumIn() != 2 || et.NumOut() != 2 {
panic("encode function must have signature func(T, string) (any, error)")
}
targetType := dt.Out(0)
sourceType := et.In(0)
return func(f *Format) {
dv := reflect.ValueOf(decodeFn)
f.decoders[targetType] = func(v any, tag string) (any, error) {
results := dv.Call([]reflect.Value{reflect.ValueOf(v), reflect.ValueOf(tag)})
err, _ := results[1].Interface().(error)
return results[0].Interface(), err
}
ev := reflect.ValueOf(encodeFn)
f.encoders[sourceType] = func(v any, tag string) (any, error) {
results := ev.Call([]reflect.Value{reflect.ValueOf(v), reflect.ValueOf(tag)})
err, _ := results[1].Interface().(error)
return results[0].Interface(), err
}
}
}
func (f *Format) Decode(data any, target any) error {
v := reflect.ValueOf(target)
if v.Kind() != reflect.Pointer || v.IsNil() {
return ErrNotPointer
}
v = v.Elem()
if v.Kind() != reflect.Struct {
return ErrNotStruct
}
m, ok := data.(map[string]any)
if !ok {
return ErrNotMap
}
return f.decode(m, v)
}
func (f *Format) Encode(source any) (map[string]any, error) {
v := reflect.ValueOf(source)
if v.Kind() == reflect.Pointer {
if v.IsNil() {
return nil, ErrNilPointer
}
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return nil, ErrNotStruct
}
return f.encode(v)
}
+595
View File
@@ -0,0 +1,595 @@
package mapstructure
import (
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
)
type Inner struct {
Value int32 `nbt:"Value"`
}
type Example struct {
Name string `nbt:"Name"`
Age int32 `nbt:"Age,omitempty"`
Secret string `nbt:"Secret,required"`
Skipped string `nbt:"-"`
NoTag string
Nested Inner `nbt:"Nested"`
Flag bool `nbt:"Flag"`
}
func TestBasicDecode(t *testing.T) {
f := NewFormat("nbt")
data := map[string]any{
"Name": "test",
"Secret": "shh",
"NoTag": "fallback",
"Nested": map[string]any{"Value": int32(42)},
"Flag": int8(1),
}
var got Example
if err := f.Decode(data, &got); err != nil {
t.Fatal(err)
}
want := Example{Name: "test", Secret: "shh", NoTag: "fallback", Nested: Inner{Value: 42}, Flag: true}
if diff := cmp.Diff(want, got); diff != "" {
t.Fatal(diff)
}
}
func TestMissingRequired(t *testing.T) {
f := NewFormat("nbt")
data := map[string]any{"Name": "test"}
var got Example
if err := f.Decode(data, &got); err == nil {
t.Fatal("expected error for missing required field")
}
}
func TestOmitEmpty(t *testing.T) {
f := NewFormat("nbt")
data := map[string]any{"Secret": "shh"}
var got Example
if err := f.Decode(data, &got); err != nil {
t.Fatal(err)
}
if got.Age != 0 {
t.Fatal("omitempty field should stay zero when absent")
}
}
func TestRequireAll(t *testing.T) {
type Simple struct {
A int32 `nbt:"A"`
B int32 `nbt:"B,omitempty"`
C int32 `nbt:"C"`
}
f := NewFormat("nbt", WithRequireAll())
data := map[string]any{"A": int32(1), "C": int32(3)}
var got Simple
if err := f.Decode(data, &got); err != nil {
t.Fatal(err)
}
if got.A != 1 || got.C != 3 {
t.Fatal("unexpected values")
}
}
func TestRequireAllMissing(t *testing.T) {
type Simple struct {
A int32 `nbt:"A"`
B int32 `nbt:"B"`
}
f := NewFormat("nbt", WithRequireAll())
data := map[string]any{"A": int32(1)}
var got Simple
if err := f.Decode(data, &got); err == nil {
t.Fatal("expected error for missing field with RequireAll")
}
}
func TestRequireAllOmitEmptyWins(t *testing.T) {
type Simple struct {
A int32 `nbt:"A"`
B int32 `nbt:"B,omitempty"`
}
f := NewFormat("nbt", WithRequireAll())
data := map[string]any{"A": int32(1)}
var got Simple
if err := f.Decode(data, &got); err != nil {
t.Fatal("omitempty should override RequireAll")
}
}
func TestSkipTag(t *testing.T) {
f := NewFormat("nbt")
data := map[string]any{"Name": "test", "Secret": "shh", "Skipped": "should not appear"}
var got Example
if err := f.Decode(data, &got); err != nil {
t.Fatal(err)
}
if got.Skipped != "" {
t.Fatal("skipped field should not be set")
}
}
func TestErrorOnExtra(t *testing.T) {
f := NewFormat("nbt", WithErrorOnExtra())
data := map[string]any{"Name": "test", "Secret": "shh", "Unknown": "extra"}
var got Example
if err := f.Decode(data, &got); err == nil {
t.Fatal("expected error for extra key")
}
}
func TestNoErrorOnExtraByDefault(t *testing.T) {
f := NewFormat("nbt")
data := map[string]any{"Name": "test", "Secret": "shh", "Unknown": "extra"}
var got Example
if err := f.Decode(data, &got); err != nil {
t.Fatal("extra keys should be ignored by default")
}
}
func TestNotPointer(t *testing.T) {
f := NewFormat("nbt")
data := map[string]any{}
var got Example
if err := f.Decode(data, got); err == nil {
t.Fatal("expected error for non-pointer target")
}
}
func TestNotStruct(t *testing.T) {
f := NewFormat("nbt")
data := map[string]any{}
var got int32
if err := f.Decode(data, &got); err == nil {
t.Fatal("expected error for non-struct target")
}
}
func TestNotMap(t *testing.T) {
f := NewFormat("nbt")
var got Example
if err := f.Decode("not a map", &got); err == nil {
t.Fatal("expected error for non-map data")
}
}
func TestTagMapping(t *testing.T) {
type Renamed struct {
GoName string `nbt:"MapKey"`
}
f := NewFormat("nbt")
data := map[string]any{"MapKey": "hello"}
var got Renamed
if err := f.Decode(data, &got); err != nil {
t.Fatal(err)
}
if got.GoName != "hello" {
t.Fatal("tag-mapped field not set correctly")
}
}
func TestTypeConversion(t *testing.T) {
type Conv struct {
I8 int8 `nbt:"I8"`
I16 int16 `nbt:"I16"`
I32 int32 `nbt:"I32"`
I64 int64 `nbt:"I64"`
F32 float32 `nbt:"F32"`
F64 float64 `nbt:"F64"`
Bool bool `nbt:"Bool"`
}
f := NewFormat("nbt")
data := map[string]any{
"I8": int8(1),
"I16": int16(2),
"I32": int32(3),
"I64": int64(4),
"F32": float32(5.5),
"F64": float64(6.6),
"Bool": int8(1),
}
var got Conv
if err := f.Decode(data, &got); err != nil {
t.Fatal(err)
}
want := Conv{I8: 1, I16: 2, I32: 3, I64: 4, F32: 5.5, F64: 6.6, Bool: true}
if diff := cmp.Diff(want, got); diff != "" {
t.Fatal(diff)
}
}
func TestTypeConversionWidening(t *testing.T) {
type Wide struct {
I32FromI8 int32 `nbt:"A"`
I64FromI16 int64 `nbt:"B"`
}
f := NewFormat("nbt")
data := map[string]any{
"A": int8(10),
"B": int16(20),
}
var got Wide
if err := f.Decode(data, &got); err != nil {
t.Fatal(err)
}
want := Wide{I32FromI8: 10, I64FromI16: 20}
if diff := cmp.Diff(want, got); diff != "" {
t.Fatal(diff)
}
}
func TestSliceDecode(t *testing.T) {
type WithSlice struct {
Items []int32 `nbt:"Items"`
}
f := NewFormat("nbt")
data := map[string]any{
"Items": []any{int32(1), int32(2), int32(3)},
}
var got WithSlice
if err := f.Decode(data, &got); err != nil {
t.Fatal(err)
}
want := WithSlice{Items: []int32{1, 2, 3}}
if diff := cmp.Diff(want, got); diff != "" {
t.Fatal(diff)
}
}
func TestNestedStructDecode(t *testing.T) {
type Outer struct {
Inner Inner `nbt:"Inner"`
}
f := NewFormat("nbt")
data := map[string]any{
"Inner": map[string]any{"Value": int32(99)},
}
var got Outer
if err := f.Decode(data, &got); err != nil {
t.Fatal(err)
}
want := Outer{Inner: Inner{Value: 99}}
if diff := cmp.Diff(want, got); diff != "" {
t.Fatal(diff)
}
}
func TestEncode(t *testing.T) {
f := NewFormat("nbt")
s := Example{Name: "test", Secret: "shh", NoTag: "fallback", Nested: Inner{Value: 42}, Flag: true}
got, err := f.Encode(s)
if err != nil {
t.Fatal(err)
}
want := map[string]any{
"Name": "test",
"Secret": "shh",
"NoTag": "fallback",
"Nested": map[string]any{"Value": int32(42)},
"Flag": int8(1),
}
if diff := cmp.Diff(want, got); diff != "" {
t.Fatal(diff)
}
}
func TestEncodeOmitEmpty(t *testing.T) {
f := NewFormat("nbt")
s := Example{Name: "test", Secret: "shh"}
got, err := f.Encode(s)
if err != nil {
t.Fatal(err)
}
if _, exists := got["Age"]; exists {
t.Fatal("omitempty zero field should not appear in encoded map")
}
}
func TestEncodeSkipTag(t *testing.T) {
f := NewFormat("nbt")
s := Example{Name: "test", Secret: "shh", Skipped: "hidden"}
got, err := f.Encode(s)
if err != nil {
t.Fatal(err)
}
if _, exists := got["Skipped"]; exists {
t.Fatal("skipped field should not appear in encoded map")
}
}
func TestEncodePointer(t *testing.T) {
f := NewFormat("nbt")
s := &Example{Name: "test", Secret: "shh"}
got, err := f.Encode(s)
if err != nil {
t.Fatal(err)
}
if got["Name"] != "test" {
t.Fatal("encode from pointer should work")
}
}
func TestEncodeSlice(t *testing.T) {
type WithSlice struct {
Items []int32 `nbt:"Items"`
}
f := NewFormat("nbt")
s := WithSlice{Items: []int32{1, 2, 3}}
got, err := f.Encode(s)
if err != nil {
t.Fatal(err)
}
items, ok := got["Items"].([]any)
if !ok {
t.Fatal("slice not encoded correctly")
}
if len(items) != 3 || items[0].(int32) != 1 {
t.Fatal("slice values not correct")
}
}
func TestRoundTrip(t *testing.T) {
f := NewFormat("nbt")
original := Example{Name: "hello", Secret: "world", Nested: Inner{Value: 99}, Flag: true}
encoded, err := f.Encode(original)
if err != nil {
t.Fatal(err)
}
var decoded Example
if err := f.Decode(encoded, &decoded); err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(original, decoded); diff != "" {
t.Fatal(diff)
}
}
func TestTypeCodecDecode(t *testing.T) {
type GameMode int32
type Player struct {
Mode GameMode `nbt:"Mode"`
}
f := NewFormat("nbt",
WithTypeCodec(
func(v any, tag string) (GameMode, error) {
i, ok := v.(int32)
if !ok {
return 0, fmt.Errorf("expected int32, got %T", v)
}
return GameMode(i), nil
},
func(v GameMode, tag string) (any, error) {
return int32(v), nil
},
),
)
data := map[string]any{"Mode": int32(1)}
var got Player
if err := f.Decode(data, &got); err != nil {
t.Fatal(err)
}
if got.Mode != GameMode(1) {
t.Fatal("type codec decode did not work")
}
}
func TestTypeCodecEncode(t *testing.T) {
type GameMode int32
type Player struct {
Mode GameMode `nbt:"Mode"`
}
f := NewFormat("nbt",
WithTypeCodec(
func(v any, tag string) (GameMode, error) {
i, ok := v.(int32)
if !ok {
return 0, fmt.Errorf("expected int32, got %T", v)
}
return GameMode(i), nil
},
func(v GameMode, tag string) (any, error) {
return int32(v), nil
},
),
)
s := Player{Mode: GameMode(2)}
got, err := f.Encode(s)
if err != nil {
t.Fatal(err)
}
if got["Mode"].(int32) != 2 {
t.Fatal("type codec encode did not work")
}
}
func TestTypeCodecRoundTrip(t *testing.T) {
type GameMode int32
type Player struct {
Mode GameMode `nbt:"Mode"`
}
f := NewFormat("nbt",
WithTypeCodec(
func(v any, tag string) (GameMode, error) {
i, ok := v.(int32)
if !ok {
return 0, fmt.Errorf("expected int32, got %T", v)
}
return GameMode(i), nil
},
func(v GameMode, tag string) (any, error) {
return int32(v), nil
},
),
)
original := Player{Mode: GameMode(3)}
encoded, err := f.Encode(original)
if err != nil {
t.Fatal(err)
}
var decoded Player
if err := f.Decode(encoded, &decoded); err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(original, decoded); diff != "" {
t.Fatal(diff)
}
}
func TestTypeCodecReceivesTag(t *testing.T) {
type GameMode int32
type Player struct {
Mode GameMode `nbt:"Mode,strict"`
Friends GameMode `nbt:"Friends"`
}
strictModes := map[string]bool{}
f := NewFormat("nbt",
WithTypeCodec(
func(v any, tag string) (GameMode, error) {
strictModes[tag] = true
i, ok := v.(int32)
if !ok {
return 0, fmt.Errorf("expected int32, got %T", v)
}
return GameMode(i), nil
},
func(v GameMode, tag string) (any, error) {
return int32(v), nil
},
),
)
data := map[string]any{"Mode": int32(1), "Friends": int32(2)}
var got Player
if err := f.Decode(data, &got); err != nil {
t.Fatal(err)
}
if !strictModes["Mode,strict"] {
t.Fatalf("expected tag 'Mode,strict' to be passed to codec, got keys: %v", strictModes)
}
}
func TestMultipleTypeCodecs(t *testing.T) {
type GameMode int32
type Dimension int32
type Player struct {
Mode GameMode `nbt:"Mode"`
Dim Dimension `nbt:"Dim"`
}
f := NewFormat("nbt",
WithTypeCodec(
func(v any, tag string) (GameMode, error) {
i, _ := v.(int32)
return GameMode(i), nil
},
func(v GameMode, tag string) (any, error) {
return int32(v), nil
},
),
WithTypeCodec(
func(v any, tag string) (Dimension, error) {
i, _ := v.(int32)
return Dimension(i), nil
},
func(v Dimension, tag string) (any, error) {
return int32(v), nil
},
),
)
data := map[string]any{"Mode": int32(1), "Dim": int32(2)}
var got Player
if err := f.Decode(data, &got); err != nil {
t.Fatal(err)
}
if got.Mode != GameMode(1) || got.Dim != Dimension(2) {
t.Fatal("multiple type codecs did not work")
}
}
func TestUnexportedFieldsIgnored(t *testing.T) {
type WithUnexported struct {
Exported string `nbt:"Exported"`
unexported string
}
f := NewFormat("nbt")
data := map[string]any{"Exported": "yes", "unexported": "no"}
var got WithUnexported
if err := f.Decode(data, &got); err != nil {
t.Fatal(err)
}
if got.unexported != "" {
t.Fatal("unexported fields should be ignored")
}
}
func TestEncodeMapField(t *testing.T) {
type WithMap struct {
Props map[string]string `nbt:"Props"`
}
f := NewFormat("nbt")
s := WithMap{Props: map[string]string{"key": "val"}}
got, err := f.Encode(s)
if err != nil {
t.Fatal(err)
}
props, ok := got["Props"].(map[string]any)
if !ok {
t.Fatal("map field not encoded correctly")
}
if props["key"] != "val" {
t.Fatal("map field value incorrect")
}
}
func TestDecodeMapField(t *testing.T) {
type WithMap struct {
Props map[string]string `nbt:"Props"`
}
f := NewFormat("nbt")
data := map[string]any{
"Props": map[string]any{"key": "val"},
}
var got WithMap
if err := f.Decode(data, &got); err != nil {
t.Fatal(err)
}
if got.Props["key"] != "val" {
t.Fatal("map field value incorrect")
}
}
func TestDecodeBoolFromInt32(t *testing.T) {
type WithBool struct {
Flag bool `nbt:"Flag"`
}
f := NewFormat("nbt")
data := map[string]any{"Flag": int32(1)}
var got WithBool
if err := f.Decode(data, &got); err != nil {
t.Fatal(err)
}
if !got.Flag {
t.Fatal("bool from int32 should be true")
}
}
+28
View File
@@ -0,0 +1,28 @@
package mapstructure
import "strings"
type tagOptions struct {
Name string
OmitEmpty bool
Required bool
Skip bool
Raw string
}
func parseTag(raw string) tagOptions {
if raw == "-" {
return tagOptions{Skip: true, Raw: raw}
}
parts := strings.Split(raw, ",")
opts := tagOptions{Name: parts[0], Raw: raw}
for _, part := range parts[1:] {
switch part {
case "omitempty":
opts.OmitEmpty = true
case "required":
opts.Required = true
}
}
return opts
}