Skip to content

Commit

Permalink
Support alias type's ToStringE
Browse files Browse the repository at this point in the history
  • Loading branch information
zhangyongding committed Dec 5, 2024
1 parent 487df00 commit 985dbf9
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 54 deletions.
54 changes: 51 additions & 3 deletions cast_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,34 +244,69 @@ func TestToStringE(t *testing.T) {
}
key := &Key{"foo"}

type Int int
type Int8 int8
type Int16 int16
type Int32 int32
type Int64 int64
type Uint uint
type Uint8 uint8
type Uint16 uint16
type Uint32 uint32
type Uint64 uint64
type Float32 float32
type Float64 float64
type String string
type Bool bool

var i8 int = 8

tests := []struct {
input interface{}
expect string
iserr bool
}{
{int(8), "8", false},
{&i8, "8", false},
{int8(8), "8", false},
{int16(8), "8", false},
{int32(8), "8", false},
{int64(8), "8", false},
{Int(8), "8", false},
{Int8(8), "8", false},
{Int16(8), "8", false},
{Int32(8), "8", false},
{Int64(8), "8", false},
{uint(8), "8", false},
{uint8(8), "8", false},
{uint16(8), "8", false},
{uint32(8), "8", false},
{uint64(8), "8", false},
{Uint(8), "8", false},
{Uint8(8), "8", false},
{Uint16(8), "8", false},
{Uint32(8), "8", false},
{Uint64(8), "8", false},
{float32(8.31), "8.31", false},
{float64(8.31), "8.31", false},
{Float32(8.31), "8.31", false},
{Float64(8.31), "8.31", false},
{jn, "8", false},
{true, "true", false},
{false, "false", false},
{Bool(true), "true", false},
{Bool(false), "false", false},
{nil, "", false},
{[]byte("one time"), "one time", false},
{"one more time", "one more time", false},
{String("one more time"), "one more time", false},
{template.HTML("one time"), "one time", false},
{template.URL("http://somehost.foo"), "http://somehost.foo", false},
{template.JS("(1+2)"), "(1+2)", false},
{template.CSS("a"), "a", false},
{template.HTMLAttr("a"), "a", false},
{time.Second, "1s", false},
{errors.New("error"), "error", false},
// errors
{testing.T{}, "", true},
{key, "", true},
Expand Down Expand Up @@ -873,18 +908,31 @@ func TestToBoolE(t *testing.T) {
}
}

func BenchmarkTooBool(b *testing.B) {
func BenchmarkToBool(b *testing.B) {
for i := 0; i < b.N; i++ {
if !ToBool(true) {
b.Fatal("ToBool returned false")
}
}
}

func BenchmarkTooInt(b *testing.B) {
func BenchmarkToInt(b *testing.B) {
convert := func(num52 interface{}) {
if v := ToInt(num52); v != 52 {
b.Fatalf("ToInt returned wrong value, got %d, want %d", v, 32)
b.Fatalf("ToInt returned wrong value, got %d, want %d", v, 52)
}
}
for i := 0; i < b.N; i++ {
convert("52")
convert(52.0)
convert(uint64(52))
}
}

func BenchmarkToString(b *testing.B) {
convert := func(i interface{}) {
if v := ToString(i); v == "" {
b.Fatalf("ToInt returned wrong value, got %v", v)
}
}
for i := 0; i < b.N; i++ {
Expand Down
73 changes: 22 additions & 51 deletions caste.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"encoding/json"
"errors"
"fmt"
"html/template"
"reflect"
"strconv"
"strings"
Expand Down Expand Up @@ -922,80 +921,52 @@ func indirect(a interface{}) interface{} {
return v.Interface()
}

// From html/template/content.go
// Copyright 2011 The Go Authors. All rights reserved.
// indirectToStringerOrError returns the value, after dereferencing as many times
// indirectToStringerOrError returns the reflect value, after dereferencing as many times
// as necessary to reach the base type (or nil) or an implementation of fmt.Stringer
// or error,
func indirectToStringerOrError(a interface{}) interface{} {
if a == nil {
return nil
}

func indirectToStringerOrError(a interface{}) reflect.Value {
errorType := reflect.TypeOf((*error)(nil)).Elem()
fmtStringerType := reflect.TypeOf((*fmt.Stringer)(nil)).Elem()

v := reflect.ValueOf(a)
for !v.Type().Implements(fmtStringerType) && !v.Type().Implements(errorType) && v.Kind() == reflect.Ptr && !v.IsNil() {
v = v.Elem()
}
return v.Interface()
return v
}

// ToStringE casts an interface to a string type.
func ToStringE(i interface{}) (string, error) {
i = indirectToStringerOrError(i)
if i == nil {
return "", nil
}
v := indirectToStringerOrError(i)
i = v.Interface()

switch s := i.(type) {
case string:
return s, nil
case bool:
return strconv.FormatBool(s), nil
case float64:
return strconv.FormatFloat(s, 'f', -1, 64), nil
case float32:
return strconv.FormatFloat(float64(s), 'f', -1, 32), nil
case int:
return strconv.Itoa(s), nil
case int64:
return strconv.FormatInt(s, 10), nil
case int32:
return strconv.Itoa(int(s)), nil
case int16:
return strconv.FormatInt(int64(s), 10), nil
case int8:
return strconv.FormatInt(int64(s), 10), nil
case uint:
return strconv.FormatUint(uint64(s), 10), nil
case uint64:
return strconv.FormatUint(uint64(s), 10), nil
case uint32:
return strconv.FormatUint(uint64(s), 10), nil
case uint16:
return strconv.FormatUint(uint64(s), 10), nil
case uint8:
return strconv.FormatUint(uint64(s), 10), nil
case json.Number:
return s.String(), nil
case []byte:
return string(s), nil
case template.HTML:
return string(s), nil
case template.URL:
return string(s), nil
case template.JS:
return string(s), nil
case template.CSS:
return string(s), nil
case template.HTMLAttr:
return string(s), nil
case nil:
return "", nil
case fmt.Stringer:
return s.String(), nil
case error:
return s.Error(), nil
default:
switch v.Kind() {
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return strconv.FormatUint(v.Uint(), 10), nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(v.Int(), 10), nil
case reflect.Bool:
return strconv.FormatBool(v.Bool()), nil
case reflect.Float32:
return strconv.FormatFloat(v.Float(), 'f', -1, 32), nil
case reflect.Float64:
return strconv.FormatFloat(v.Float(), 'f', -1, 64), nil
case reflect.String:
return v.String(), nil
}
return "", fmt.Errorf("unable to cast %#v of type %T to string", i, i)
}
}
Expand Down

0 comments on commit 985dbf9

Please sign in to comment.