mirror of
https://gitee.com/johng/gf
synced 2026-07-07 14:25:17 +08:00
merge develop
This commit is contained in:
@ -36,7 +36,7 @@ func New(safe ...bool) *Array {
|
||||
return NewArraySize(0, 0, safe...)
|
||||
}
|
||||
|
||||
// See New.
|
||||
// NewArray is alias of New, please see New.
|
||||
func NewArray(safe ...bool) *Array {
|
||||
return NewArraySize(0, 0, safe...)
|
||||
}
|
||||
@ -422,7 +422,7 @@ func (a *Array) SubSlice(offset int, length ...int) []interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
// See PushRight.
|
||||
// Append is alias of PushRight, please See PushRight.
|
||||
func (a *Array) Append(value ...interface{}) *Array {
|
||||
a.PushRight(value...)
|
||||
return a
|
||||
|
||||
@ -149,10 +149,7 @@ func (a *IntArray) Sort(reverse ...bool) *IntArray {
|
||||
defer a.mu.Unlock()
|
||||
if len(reverse) > 0 && reverse[0] {
|
||||
sort.Slice(a.array, func(i, j int) bool {
|
||||
if a.array[i] < a.array[j] {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
return a.array[i] >= a.array[j]
|
||||
})
|
||||
} else {
|
||||
sort.Ints(a.array)
|
||||
@ -427,7 +424,7 @@ func (a *IntArray) SubSlice(offset int, length ...int) []int {
|
||||
}
|
||||
}
|
||||
|
||||
// See PushRight.
|
||||
// Append is alias of PushRight,please See PushRight.
|
||||
func (a *IntArray) Append(value ...int) *IntArray {
|
||||
a.mu.Lock()
|
||||
a.array = append(a.array, value...)
|
||||
|
||||
@ -136,10 +136,7 @@ func (a *StrArray) Sort(reverse ...bool) *StrArray {
|
||||
defer a.mu.Unlock()
|
||||
if len(reverse) > 0 && reverse[0] {
|
||||
sort.Slice(a.array, func(i, j int) bool {
|
||||
if strings.Compare(a.array[i], a.array[j]) < 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
return strings.Compare(a.array[i], a.array[j]) >= 0
|
||||
})
|
||||
} else {
|
||||
sort.Strings(a.array)
|
||||
@ -414,7 +411,7 @@ func (a *StrArray) SubSlice(offset int, length ...int) []string {
|
||||
}
|
||||
}
|
||||
|
||||
// See PushRight.
|
||||
// Append is alias of PushRight,please See PushRight.
|
||||
func (a *StrArray) Append(value ...string) *StrArray {
|
||||
a.mu.Lock()
|
||||
a.array = append(a.array, value...)
|
||||
|
||||
@ -156,9 +156,7 @@ func (a *SortedArray) Append(values ...interface{}) *SortedArray {
|
||||
if cmp > 0 {
|
||||
index++
|
||||
}
|
||||
rear := append([]interface{}{}, a.array[index:]...)
|
||||
a.array = append(a.array[0:index], value)
|
||||
a.array = append(a.array, rear...)
|
||||
a.array = append(a.array[:index], append([]interface{}{value}, a.array[index:]...)...)
|
||||
}
|
||||
return a
|
||||
}
|
||||
@ -453,7 +451,7 @@ func (a *SortedArray) binSearch(value interface{}, lock bool) (index int, result
|
||||
mid := 0
|
||||
cmp := -2
|
||||
for min <= max {
|
||||
mid = min + int((max-min)/2)
|
||||
mid = min + (max-min)/2
|
||||
cmp = a.getComparator()(value, a.array[mid])
|
||||
switch {
|
||||
case cmp < 0:
|
||||
|
||||
39
container/garray/garray_z_bench_any_test.go
Normal file
39
container/garray/garray_z_bench_any_test.go
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package garray_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type anySortedArrayItem struct {
|
||||
priority int64
|
||||
value interface{}
|
||||
}
|
||||
|
||||
var (
|
||||
anyArray = garray.NewArray()
|
||||
anySortedArray = garray.NewSortedArray(func(a, b interface{}) int {
|
||||
return int(a.(anySortedArrayItem).priority - b.(anySortedArrayItem).priority)
|
||||
})
|
||||
)
|
||||
|
||||
func Benchmark_AnyArray_Add(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
anyArray.Append(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_AnySortedArray_Add(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
anySortedArray.Add(anySortedArrayItem{
|
||||
priority: int64(i),
|
||||
value: i,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -455,9 +455,8 @@ func (c *Core) formatOnDuplicate(columns []string, option DoInsertOption) string
|
||||
}
|
||||
} else {
|
||||
for _, column := range columns {
|
||||
// If it's SAVE operation,
|
||||
// do not automatically update the creating time.
|
||||
if c.isSoftCreatedFilledName(column) {
|
||||
// If it's SAVE operation, do not automatically update the creating time.
|
||||
if c.isSoftCreatedFieldName(column) {
|
||||
continue
|
||||
}
|
||||
if len(onDuplicateStr) > 0 {
|
||||
@ -679,8 +678,8 @@ func (c *Core) HasTable(name string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// isSoftCreatedFilledName checks and returns whether given filed name is an automatic-filled created time.
|
||||
func (c *Core) isSoftCreatedFilledName(fieldName string) bool {
|
||||
// isSoftCreatedFieldName checks and returns whether given filed name is an automatic-filled created time.
|
||||
func (c *Core) isSoftCreatedFieldName(fieldName string) bool {
|
||||
if fieldName == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -217,7 +217,6 @@ func (m *Model) doInsertWithOption(insertOption int) (result sql.Result, err err
|
||||
nowString = gtime.Now().String()
|
||||
fieldNameCreate = m.getSoftFieldNameCreated()
|
||||
fieldNameUpdate = m.getSoftFieldNameUpdated()
|
||||
fieldNameDelete = m.getSoftFieldNameDeleted()
|
||||
)
|
||||
newData, err := m.filterDataForInsertOrUpdate(m.data)
|
||||
if err != nil {
|
||||
@ -286,7 +285,6 @@ func (m *Model) doInsertWithOption(insertOption int) (result sql.Result, err err
|
||||
// Automatic handling for creating/updating time.
|
||||
if !m.unscoped && (fieldNameCreate != "" || fieldNameUpdate != "") {
|
||||
for k, v := range list {
|
||||
gutil.MapDelete(v, fieldNameCreate, fieldNameUpdate, fieldNameDelete)
|
||||
if fieldNameCreate != "" {
|
||||
v[fieldNameCreate] = nowString
|
||||
}
|
||||
|
||||
@ -15,7 +15,6 @@ import (
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
)
|
||||
|
||||
// Update does "UPDATE ... " statement for the model.
|
||||
@ -43,9 +42,7 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
}
|
||||
var (
|
||||
updateData = m.data
|
||||
fieldNameCreate = m.getSoftFieldNameCreated()
|
||||
fieldNameUpdate = m.getSoftFieldNameUpdated()
|
||||
fieldNameDelete = m.getSoftFieldNameDeleted()
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false, false)
|
||||
)
|
||||
// Automatically update the record updating time.
|
||||
@ -61,7 +58,6 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
switch refKind {
|
||||
case reflect.Map, reflect.Struct:
|
||||
dataMap := ConvertDataForTableRecord(m.data)
|
||||
gutil.MapDelete(dataMap, fieldNameCreate, fieldNameUpdate, fieldNameDelete)
|
||||
if fieldNameUpdate != "" {
|
||||
dataMap[fieldNameUpdate] = gtime.Now().String()
|
||||
}
|
||||
|
||||
@ -663,6 +663,9 @@ CREATE TABLE %s (
|
||||
}
|
||||
defer dropTable(table)
|
||||
|
||||
//db.SetDebug(true)
|
||||
//defer db.SetDebug(false)
|
||||
|
||||
type Entity struct {
|
||||
Id uint64 `orm:"id,primary" json:"id"`
|
||||
Name string `orm:"name" json:"name"`
|
||||
@ -679,7 +682,7 @@ CREATE TABLE %s (
|
||||
UpdateAt: nil,
|
||||
DeleteAt: nil,
|
||||
}
|
||||
r, err := db.Model(table).Data(dataInsert).Insert()
|
||||
r, err := db.Model(table).Data(dataInsert).OmitEmpty().Insert()
|
||||
t.AssertNil(err)
|
||||
n, _ := r.RowsAffected()
|
||||
t.Assert(n, 1)
|
||||
@ -702,7 +705,7 @@ CREATE TABLE %s (
|
||||
UpdateAt: nil,
|
||||
DeleteAt: nil,
|
||||
}
|
||||
r, err = db.Model(table).Data(dataSave).Save()
|
||||
r, err = db.Model(table).Data(dataSave).OmitEmpty().Save()
|
||||
t.AssertNil(err)
|
||||
n, _ = r.RowsAffected()
|
||||
t.Assert(n, 2)
|
||||
@ -726,7 +729,7 @@ CREATE TABLE %s (
|
||||
UpdateAt: nil,
|
||||
DeleteAt: nil,
|
||||
}
|
||||
r, err = db.Model(table).Data(dataUpdate).WherePri(1).Update()
|
||||
r, err = db.Model(table).Data(dataUpdate).WherePri(1).OmitEmpty().Update()
|
||||
t.AssertNil(err)
|
||||
n, _ = r.RowsAffected()
|
||||
t.Assert(n, 1)
|
||||
@ -747,7 +750,7 @@ CREATE TABLE %s (
|
||||
UpdateAt: nil,
|
||||
DeleteAt: nil,
|
||||
}
|
||||
r, err = db.Model(table).Data(dataReplace).Replace()
|
||||
r, err = db.Model(table).Data(dataReplace).OmitEmpty().Replace()
|
||||
t.AssertNil(err)
|
||||
n, _ = r.RowsAffected()
|
||||
t.Assert(n, 2)
|
||||
|
||||
@ -142,10 +142,14 @@ func WrapSkipf(skip int, err error, format string, args ...interface{}) error {
|
||||
}
|
||||
|
||||
// NewCode creates and returns an error that has error code and given text.
|
||||
func NewCode(code int, text string) error {
|
||||
func NewCode(code int, text ...string) error {
|
||||
errText := ""
|
||||
if len(text) > 0 {
|
||||
errText = text[0]
|
||||
}
|
||||
return &Error{
|
||||
stack: callers(),
|
||||
text: text,
|
||||
text: errText,
|
||||
code: code,
|
||||
}
|
||||
}
|
||||
@ -161,10 +165,14 @@ func NewCodef(code int, format string, args ...interface{}) error {
|
||||
|
||||
// NewCodeSkip creates and returns an error which has error code and is formatted from given text.
|
||||
// The parameter <skip> specifies the stack callers skipped amount.
|
||||
func NewCodeSkip(code, skip int, text string) error {
|
||||
func NewCodeSkip(code, skip int, text ...string) error {
|
||||
errText := ""
|
||||
if len(text) > 0 {
|
||||
errText = text[0]
|
||||
}
|
||||
return &Error{
|
||||
stack: callers(skip),
|
||||
text: text,
|
||||
text: errText,
|
||||
code: code,
|
||||
}
|
||||
}
|
||||
@ -181,14 +189,18 @@ func NewCodeSkipf(code, skip int, format string, args ...interface{}) error {
|
||||
|
||||
// WrapCode wraps error with code and text.
|
||||
// It returns nil if given err is nil.
|
||||
func WrapCode(code int, err error, text string) error {
|
||||
func WrapCode(code int, err error, text ...string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
errText := ""
|
||||
if len(text) > 0 {
|
||||
errText = text[0]
|
||||
}
|
||||
return &Error{
|
||||
error: err,
|
||||
stack: callers(),
|
||||
text: text,
|
||||
text: errText,
|
||||
code: code,
|
||||
}
|
||||
}
|
||||
@ -210,14 +222,18 @@ func WrapCodef(code int, err error, format string, args ...interface{}) error {
|
||||
// WrapCodeSkip wraps error with code and text.
|
||||
// It returns nil if given err is nil.
|
||||
// The parameter <skip> specifies the stack callers skipped amount.
|
||||
func WrapCodeSkip(code, skip int, err error, text string) error {
|
||||
func WrapCodeSkip(code, skip int, err error, text ...string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
errText := ""
|
||||
if len(text) > 0 {
|
||||
errText = text[0]
|
||||
}
|
||||
return &Error{
|
||||
error: err,
|
||||
stack: callers(skip),
|
||||
text: text,
|
||||
text: errText,
|
||||
code: code,
|
||||
}
|
||||
}
|
||||
@ -238,14 +254,24 @@ func WrapCodeSkipf(code, skip int, err error, format string, args ...interface{}
|
||||
}
|
||||
|
||||
// Code returns the error code of current error.
|
||||
// It returns -1 if it has no error code or it does not implements interface Code.
|
||||
// It returns CodeNil if it has no error code or it does not implements interface Code.
|
||||
func Code(err error) int {
|
||||
if err != nil {
|
||||
if e, ok := err.(apiCode); ok {
|
||||
return e.Code()
|
||||
}
|
||||
}
|
||||
return -1
|
||||
return CodeNil
|
||||
}
|
||||
|
||||
// CodeMessage retrieves and returns the error code message from given error.
|
||||
func CodeMessage(err error) string {
|
||||
return Message(Code(err))
|
||||
}
|
||||
|
||||
// Message returns the message string for specified code.
|
||||
func Message(code int) string {
|
||||
return codeMessageMap[code]
|
||||
}
|
||||
|
||||
// Cause returns the root cause error of <err>.
|
||||
|
||||
@ -9,32 +9,62 @@ package gerror
|
||||
// Reserved internal error code of framework: code < 1000.
|
||||
|
||||
const (
|
||||
// ===============================================================================
|
||||
// Common system codes.
|
||||
// ===============================================================================
|
||||
|
||||
CodeNil = -1 // No error code specified.
|
||||
CodeOk = 0 // It is OK.
|
||||
CodeInternalError = 50 + iota // An error occurred internally.
|
||||
CodeValidationFailed // Data validation failed.
|
||||
CodeDbOperationError // Database operation error.
|
||||
CodeInvalidParameter // The given parameter for current operation is invalid.
|
||||
CodeMissingParameter // Parameter for current operation is missing.
|
||||
CodeInvalidOperation // The function cannot be used like this.
|
||||
CodeInvalidConfiguration // The configuration is invalid for current operation.
|
||||
CodeMissingConfiguration // The configuration is missing for current operation.
|
||||
CodeNotImplemented // The operation is not implemented yet.
|
||||
CodeNotSupported // The operation is not supported yet.
|
||||
CodeOperationFailed // I tried, but I cannot give you what you want.
|
||||
CodeNotAuthorized // Not Authorized.
|
||||
CodeSecurityReason // Security Reason.
|
||||
CodeServerBusy // Server is busy, please try again later.
|
||||
CodeUnknown // Unknown error.
|
||||
CodeResourceNotExist // Resource does not exist.
|
||||
|
||||
// ===============================================================================
|
||||
// Common business codes.
|
||||
// ===============================================================================
|
||||
|
||||
CodeBusinessValidationFailed = 300 + iota // Business validation failed.
|
||||
CodeNil = -1 // No error code specified.
|
||||
CodeOk = 0 // It is OK.
|
||||
CodeInternalError = 50 // An error occurred internally.
|
||||
CodeValidationFailed = 51 // Data validation failed.
|
||||
CodeDbOperationError = 52 // Database operation error.
|
||||
CodeInvalidParameter = 53 // The given parameter for current operation is invalid.
|
||||
CodeMissingParameter = 54 // Parameter for current operation is missing.
|
||||
CodeInvalidOperation = 55 // The function cannot be used like this.
|
||||
CodeInvalidConfiguration = 56 // The configuration is invalid for current operation.
|
||||
CodeMissingConfiguration = 57 // The configuration is missing for current operation.
|
||||
CodeNotImplemented = 58 // The operation is not implemented yet.
|
||||
CodeNotSupported = 59 // The operation is not supported yet.
|
||||
CodeOperationFailed = 60 // I tried, but I cannot give you what you want.
|
||||
CodeNotAuthorized = 61 // Not Authorized.
|
||||
CodeSecurityReason = 62 // Security Reason.
|
||||
CodeServerBusy = 63 // Server is busy, please try again later.
|
||||
CodeUnknown = 64 // Unknown error.
|
||||
CodeResourceNotExist = 65 // Resource does not exist.
|
||||
CodeInvalidRequest = 66 // Invalid request.
|
||||
CodeBusinessValidationFailed = 300 // Business validation failed.
|
||||
)
|
||||
|
||||
var (
|
||||
// codeMessageMap is the mapping from code to according string message.
|
||||
codeMessageMap = map[int]string{
|
||||
CodeNil: "",
|
||||
CodeOk: "OK",
|
||||
CodeInternalError: "Internal Error",
|
||||
CodeValidationFailed: "Validation Failed",
|
||||
CodeDbOperationError: "Database Operation Error",
|
||||
CodeInvalidParameter: "Invalid Parameter",
|
||||
CodeMissingParameter: "Missing Parameter",
|
||||
CodeInvalidOperation: "Invalid Operation",
|
||||
CodeInvalidConfiguration: "Invalid Configuration",
|
||||
CodeMissingConfiguration: "Missing Configuration",
|
||||
CodeNotImplemented: "Not Implemented",
|
||||
CodeNotSupported: "Not Supported",
|
||||
CodeOperationFailed: "Operation Failed",
|
||||
CodeNotAuthorized: "Not Authorized",
|
||||
CodeSecurityReason: "Security Reason",
|
||||
CodeServerBusy: "Server Is Busy",
|
||||
CodeUnknown: "Unknown Error",
|
||||
CodeResourceNotExist: "Resource Not Exist",
|
||||
CodeInvalidRequest: "Invalid Request",
|
||||
CodeBusinessValidationFailed: "Business Validation Failed",
|
||||
}
|
||||
)
|
||||
|
||||
// RegisterCode registers custom error code to global error codes for gerror recognition.
|
||||
func RegisterCode(code int, message string) {
|
||||
codeMessageMap[code] = message
|
||||
}
|
||||
|
||||
// RegisterCodeMap registers custom error codes to global error codes for gerror recognition using map.
|
||||
func RegisterCodeMap(codes map[int]string) {
|
||||
for k, v := range codes {
|
||||
codeMessageMap[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,8 +47,11 @@ func (err *Error) Error() string {
|
||||
return ""
|
||||
}
|
||||
errStr := err.text
|
||||
if errStr == "" {
|
||||
errStr = Message(err.code)
|
||||
}
|
||||
if err.error != nil {
|
||||
if err.text != "" {
|
||||
if errStr != "" {
|
||||
errStr += ": "
|
||||
}
|
||||
errStr += err.error.Error()
|
||||
@ -57,10 +60,10 @@ func (err *Error) Error() string {
|
||||
}
|
||||
|
||||
// Code returns the error code.
|
||||
// It returns -1 if it has no error code.
|
||||
// It returns CodeNil if it has no error code.
|
||||
func (err *Error) Code() int {
|
||||
if err == nil {
|
||||
return -1
|
||||
return CodeNil
|
||||
}
|
||||
return err.code
|
||||
}
|
||||
@ -159,6 +162,7 @@ func (err *Error) Current() error {
|
||||
error: nil,
|
||||
stack: err.stack,
|
||||
text: err.text,
|
||||
code: err.code,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -261,8 +261,8 @@ func Test_Code(t *testing.T) {
|
||||
t.Assert(err.Error(), "123")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := gerror.NewCode(1, "123")
|
||||
t.Assert(gerror.Code(err), 1)
|
||||
err := gerror.NewCode(gerror.CodeUnknown, "123")
|
||||
t.Assert(gerror.Code(err), gerror.CodeUnknown)
|
||||
t.Assert(err.Error(), "123")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
@ -318,3 +318,36 @@ func Test_Json(t *testing.T) {
|
||||
t.Assert(string(b), `"2: 1"`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Message(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(gerror.Message(-100), ``)
|
||||
t.Assert(gerror.Message(gerror.CodeNil), ``)
|
||||
t.Assert(gerror.Message(gerror.CodeOk), `OK`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CodeMessage(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := gerror.NewCode(gerror.CodeUnknown)
|
||||
t.Assert(gerror.CodeMessage(err), `Unknown Error`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_RegisterCode(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
gerror.RegisterCode(10086, "MobileTelecom")
|
||||
t.Assert(gerror.Message(10086), `MobileTelecom`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_RegisterCodeMap(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
gerror.RegisterCodeMap(map[int]string{
|
||||
10087: "MobileTelecom 10087",
|
||||
10088: "MobileTelecom 10088",
|
||||
})
|
||||
t.Assert(gerror.Message(10087), `MobileTelecom 10087`)
|
||||
t.Assert(gerror.Message(10088), `MobileTelecom 10088`)
|
||||
})
|
||||
}
|
||||
|
||||
@ -141,7 +141,7 @@ func Database(name ...string) gdb.DB {
|
||||
}
|
||||
return db
|
||||
} else {
|
||||
// It panics often because it dose not find its configuration for given group.
|
||||
// If panics, often because it does not find its configuration for given group.
|
||||
panic(err)
|
||||
}
|
||||
return nil
|
||||
|
||||
@ -37,12 +37,12 @@ func niceCallFunc(f func()) {
|
||||
// of the real error point.
|
||||
if err, ok := exception.(error); ok {
|
||||
if gerror.Code(err) != gerror.CodeNil {
|
||||
panic(gerror.Wrap(err, ""))
|
||||
panic(err)
|
||||
} else {
|
||||
panic(gerror.WrapCode(gerror.CodeInternalError, err, ""))
|
||||
panic(gerror.WrapCodeSkip(gerror.CodeInternalError, 1, err, ""))
|
||||
}
|
||||
} else {
|
||||
panic(gerror.NewCodeSkipf(gerror.CodeInternalError, 1, "%v", exception))
|
||||
panic(gerror.NewCodeSkipf(gerror.CodeInternalError, 1, "%+v", exception))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,33 +133,33 @@ func (m *middleware) callHandlerFunc(funcInfo handlerFuncInfo) {
|
||||
}
|
||||
if funcInfo.Type.NumIn() == 2 {
|
||||
var (
|
||||
request reflect.Value
|
||||
inputObject reflect.Value
|
||||
)
|
||||
if funcInfo.Type.In(1).Kind() == reflect.Ptr {
|
||||
request = reflect.New(funcInfo.Type.In(1).Elem())
|
||||
m.request.handlerResponse.Error = m.request.Parse(request.Interface())
|
||||
inputObject = reflect.New(funcInfo.Type.In(1).Elem())
|
||||
m.request.handlerResponse.Error = m.request.Parse(inputObject.Interface())
|
||||
} else {
|
||||
request = reflect.New(funcInfo.Type.In(1).Elem()).Elem()
|
||||
m.request.handlerResponse.Error = m.request.Parse(request.Addr().Interface())
|
||||
inputObject = reflect.New(funcInfo.Type.In(1).Elem()).Elem()
|
||||
m.request.handlerResponse.Error = m.request.Parse(inputObject.Addr().Interface())
|
||||
}
|
||||
if m.request.handlerResponse.Error != nil {
|
||||
return
|
||||
}
|
||||
inputValues = append(inputValues, request)
|
||||
inputValues = append(inputValues, inputObject)
|
||||
}
|
||||
|
||||
// Call handler with dynamic created parameter values.
|
||||
results := funcInfo.Value.Call(inputValues)
|
||||
switch len(results) {
|
||||
case 1:
|
||||
m.request.handlerResponse.Error = results[0].Interface().(error)
|
||||
if !results[0].IsNil() {
|
||||
m.request.handlerResponse.Error = results[0].Interface().(error)
|
||||
}
|
||||
|
||||
case 2:
|
||||
m.request.handlerResponse.Object = results[0].Interface()
|
||||
if !results[1].IsNil() {
|
||||
if v := results[1].Interface(); v != nil {
|
||||
m.request.handlerResponse.Error = v.(error)
|
||||
}
|
||||
m.request.handlerResponse.Error = results[1].Interface().(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ import (
|
||||
"github.com/gogf/gf/encoding/gjson"
|
||||
"github.com/gogf/gf/encoding/gurl"
|
||||
"github.com/gogf/gf/encoding/gxml"
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/internal/utils"
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
@ -299,7 +300,7 @@ func (r *Request) parseQuery() {
|
||||
var err error
|
||||
r.queryMap, err = gstr.Parse(r.URL.RawQuery)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
panic(gerror.WrapCode(gerror.CodeInvalidParameter, err, ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -354,12 +355,12 @@ func (r *Request) parseForm() {
|
||||
if gstr.Contains(contentType, "multipart/") {
|
||||
// multipart/form-data, multipart/mixed
|
||||
if err = r.ParseMultipartForm(r.Server.config.FormParsingMemory); err != nil {
|
||||
panic(err)
|
||||
panic(gerror.WrapCode(gerror.CodeInvalidRequest, err, ""))
|
||||
}
|
||||
} else if gstr.Contains(contentType, "form") {
|
||||
// application/x-www-form-urlencoded
|
||||
if err = r.Request.ParseForm(); err != nil {
|
||||
panic(err)
|
||||
panic(gerror.WrapCode(gerror.CodeInvalidRequest, err, ""))
|
||||
}
|
||||
}
|
||||
if len(r.PostForm) > 0 {
|
||||
@ -402,7 +403,7 @@ func (r *Request) parseForm() {
|
||||
}
|
||||
if params != "" {
|
||||
if r.formMap, err = gstr.Parse(params); err != nil {
|
||||
panic(err)
|
||||
panic(gerror.WrapCode(gerror.CodeInvalidParameter, err, ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,13 +17,15 @@ import (
|
||||
// NOTE THAT the page parameter name from client is constantly defined as gpage.DefaultPageName
|
||||
// for simplification and convenience.
|
||||
func (r *Request) GetPage(totalSize, pageSize int) *gpage.Page {
|
||||
// It must has Router object attribute.
|
||||
// It must have Router object attribute.
|
||||
if r.Router == nil {
|
||||
panic("Router object not found")
|
||||
}
|
||||
url := *r.URL
|
||||
urlTemplate := url.Path
|
||||
uriHasPageName := false
|
||||
var (
|
||||
url = *r.URL
|
||||
urlTemplate = url.Path
|
||||
uriHasPageName = false
|
||||
)
|
||||
// Check the page variable in the URI.
|
||||
if len(r.Router.RegNames) > 0 {
|
||||
for _, name := range r.Router.RegNames {
|
||||
@ -39,12 +41,17 @@ func (r *Request) GetPage(totalSize, pageSize int) *gpage.Page {
|
||||
for i, name := range r.Router.RegNames {
|
||||
rule := fmt.Sprintf(`[:\*]%s|\{%s\}`, name, name)
|
||||
if name == gpage.DefaultPageName {
|
||||
urlTemplate, _ = gregex.ReplaceString(rule, gpage.DefaultPagePlaceHolder, urlTemplate)
|
||||
urlTemplate, err = gregex.ReplaceString(rule, gpage.DefaultPagePlaceHolder, urlTemplate)
|
||||
} else {
|
||||
urlTemplate, _ = gregex.ReplaceString(rule, match[i+1], urlTemplate)
|
||||
urlTemplate, err = gregex.ReplaceString(rule, match[i+1], urlTemplate)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,7 +71,7 @@ func (r *Response) WritefExit(format string, params ...interface{}) {
|
||||
r.Request.Exit()
|
||||
}
|
||||
|
||||
// Writef writes the response with <content> and new line.
|
||||
// Writeln writes the response with <content> and new line.
|
||||
func (r *Response) Writeln(content ...interface{}) {
|
||||
if len(content) == 0 {
|
||||
r.Write("\n")
|
||||
|
||||
@ -107,7 +107,7 @@ func GetServer(name ...interface{}) *Server {
|
||||
}
|
||||
// Initialize the server using default configurations.
|
||||
if err := s.SetConfig(NewConfig()); err != nil {
|
||||
panic(err)
|
||||
panic(gerror.WrapCode(gerror.CodeInvalidConfiguration, err, ""))
|
||||
}
|
||||
// Record the server to internal server mapping by name.
|
||||
serverMapping.Set(serverName, s)
|
||||
|
||||
@ -68,12 +68,12 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
request.Response.WriteStatus(http.StatusInternalServerError)
|
||||
if err, ok := exception.(error); ok {
|
||||
if code := gerror.Code(err); code != gerror.CodeNil {
|
||||
s.handleErrorLog(gerror.Wrap(err, ""), request)
|
||||
s.handleErrorLog(err, request)
|
||||
} else {
|
||||
s.handleErrorLog(gerror.WrapCode(gerror.CodeInternalError, err, ""), request)
|
||||
s.handleErrorLog(gerror.WrapCodeSkip(gerror.CodeInternalError, 1, err, ""), request)
|
||||
}
|
||||
} else {
|
||||
s.handleErrorLog(gerror.NewCodef(gerror.CodeInternalError, "%v", exception), request)
|
||||
s.handleErrorLog(gerror.NewCodeSkipf(gerror.CodeInternalError, 1, "%+v", exception), request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ package ghttp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"strings"
|
||||
|
||||
@ -175,8 +176,8 @@ func (s *Server) searchHandlers(method, path, domain string) (parsedItems []*han
|
||||
parsedItemList.PushBack(parsedItem)
|
||||
|
||||
// The middleware is inserted before the serving handler.
|
||||
// If there're multiple middleware, they're inserted into the result list by their registering order.
|
||||
// The middleware are also executed by their registered order.
|
||||
// If there are multiple middleware, they're inserted into the result list by their registering order.
|
||||
// The middleware is also executed by their registered order.
|
||||
case handlerTypeMiddleware:
|
||||
if lastMiddlewareElem == nil {
|
||||
lastMiddlewareElem = parsedItemList.PushFront(parsedItem)
|
||||
@ -190,7 +191,7 @@ func (s *Server) searchHandlers(method, path, domain string) (parsedItems []*han
|
||||
parsedItemList.PushBack(parsedItem)
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf(`invalid handler type %d`, item.Type))
|
||||
panic(gerror.NewCodef(gerror.CodeInternalError, `invalid handler type %d`, item.Type))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,7 +152,7 @@ func Test_ClientMaxBodySize_File(t *testing.T) {
|
||||
defer gfile.Remove(path)
|
||||
t.Assert(
|
||||
gstr.Trim(c.PostContent("/", "name=john&file=@file:"+path)),
|
||||
"http: request body too large",
|
||||
"Invalid Request: http: request body too large",
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@ -77,6 +77,6 @@ func Test_Router_Handler_Extended_Handler_WithObject(t *testing.T) {
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
|
||||
t.Assert(client.GetContent("/test?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Age":18,"Name":"john"}}`)
|
||||
t.Assert(client.GetContent("/test/error"), `{"code":52,"message":"error","data":null}`)
|
||||
t.Assert(client.GetContent("/test/error"), `{"code":50,"message":"error","data":null}`)
|
||||
})
|
||||
}
|
||||
|
||||
@ -22,6 +22,11 @@ func Size(path string) int64 {
|
||||
return s.Size()
|
||||
}
|
||||
|
||||
// SizeFormat returns the size of file specified by <path> in format string.
|
||||
func SizeFormat(path string) string {
|
||||
return FormatSize(Size(path))
|
||||
}
|
||||
|
||||
// ReadableSize formats size of file given by <path>, for more human readable.
|
||||
func ReadableSize(path string) string {
|
||||
return FormatSize(Size(path))
|
||||
|
||||
@ -33,6 +33,25 @@ func Test_Size(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_SizeFormat(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
paths1 = "/testfile_t1.txt"
|
||||
sizes string
|
||||
)
|
||||
|
||||
createTestFile(paths1, "abcdefghijklmn")
|
||||
defer delTestFiles(paths1)
|
||||
|
||||
sizes = gfile.SizeFormat(testpath() + paths1)
|
||||
t.Assert(sizes, "14.00B")
|
||||
|
||||
sizes = gfile.SizeFormat("")
|
||||
t.Assert(sizes, "0.00B")
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func Test_StrToSize(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(gfile.StrToSize("0.00B"), 0)
|
||||
|
||||
@ -43,6 +43,13 @@ func (l *Logger) doRotateFile(filePath string) error {
|
||||
}
|
||||
defer gmlock.Unlock(memoryLockKey)
|
||||
|
||||
intlog.PrintFunc(l.ctx, func() string {
|
||||
return fmt.Sprintf(`start rotating file by size: %s, file: %s`, gfile.SizeFormat(filePath), filePath)
|
||||
})
|
||||
defer intlog.PrintFunc(l.ctx, func() string {
|
||||
return fmt.Sprintf(`done rotating file by size: %s, size: %s`, gfile.SizeFormat(filePath), filePath)
|
||||
})
|
||||
|
||||
// No backups, it then just removes the current logging file.
|
||||
if l.config.RotateBackupLimit == 0 {
|
||||
if err := gfile.Remove(filePath); err != nil {
|
||||
@ -90,6 +97,7 @@ func (l *Logger) doRotateFile(filePath string) error {
|
||||
intlog.Printf(l.ctx, `rotation file exists, continue: %s`, newFilePath)
|
||||
}
|
||||
}
|
||||
intlog.Printf(l.ctx, "rotating file by size from %s to %s", filePath, newFilePath)
|
||||
if err := gfile.Rename(filePath, newFilePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -11,9 +11,9 @@ import (
|
||||
"github.com/gogf/gf/os/gmutex"
|
||||
)
|
||||
|
||||
// Memory locker.
|
||||
// Locker is a memory based locker.
|
||||
// Note that there's no cache expire mechanism for mutex in locker.
|
||||
// You need remove certain mutex manually when you do not want use it any more.
|
||||
// You need remove certain mutex manually when you do not want use it anymore.
|
||||
type Locker struct {
|
||||
m *gmap.StrAnyMap
|
||||
}
|
||||
@ -28,7 +28,7 @@ func New() *Locker {
|
||||
|
||||
// Lock locks the <key> with writing lock.
|
||||
// If there's a write/reading lock the <key>,
|
||||
// it will blocks until the lock is released.
|
||||
// it will block until the lock is released.
|
||||
func (l *Locker) Lock(key string) {
|
||||
l.getOrNewMutex(key).Lock()
|
||||
}
|
||||
@ -68,7 +68,7 @@ func (l *Locker) RUnlock(key string) {
|
||||
|
||||
// LockFunc locks the <key> with writing lock and callback function <f>.
|
||||
// If there's a write/reading lock the <key>,
|
||||
// it will blocks until the lock is released.
|
||||
// it will block until the lock is released.
|
||||
//
|
||||
// It releases the lock after <f> is executed.
|
||||
func (l *Locker) LockFunc(key string, f func()) {
|
||||
@ -79,7 +79,7 @@ func (l *Locker) LockFunc(key string, f func()) {
|
||||
|
||||
// RLockFunc locks the <key> with reading lock and callback function <f>.
|
||||
// If there's a writing lock the <key>,
|
||||
// it will blocks until the lock is released.
|
||||
// it will block until the lock is released.
|
||||
//
|
||||
// It releases the lock after <f> is executed.
|
||||
func (l *Locker) RLockFunc(key string, f func()) {
|
||||
|
||||
@ -24,6 +24,7 @@ import (
|
||||
|
||||
const (
|
||||
// Short writes for common usage durations.
|
||||
|
||||
D = 24 * time.Hour
|
||||
H = time.Hour
|
||||
M = time.Minute
|
||||
|
||||
@ -15,6 +15,7 @@ var (
|
||||
// locationMap is time zone name to its location object.
|
||||
// Time zone name is like: Asia/Shanghai.
|
||||
locationMap = make(map[string]*time.Location)
|
||||
|
||||
// locationMu is used for concurrent safety for `locationMap`.
|
||||
locationMu = sync.RWMutex{}
|
||||
)
|
||||
|
||||
@ -33,12 +33,12 @@ func (entry *Entry) Status() int {
|
||||
// Run runs the timer job asynchronously.
|
||||
func (entry *Entry) Run() {
|
||||
leftRunningTimes := entry.times.Add(-1)
|
||||
// Running times exceeding checks.
|
||||
// It checks its running times exceeding.
|
||||
if leftRunningTimes < 0 {
|
||||
entry.status.Set(StatusClosed)
|
||||
return
|
||||
}
|
||||
// This means it does not limit the running times.
|
||||
// This means it has no limit in running times.
|
||||
if leftRunningTimes == math.MaxInt32-1 {
|
||||
entry.times.Set(math.MaxInt32)
|
||||
}
|
||||
|
||||
@ -18,9 +18,9 @@ import (
|
||||
// high priority is served before an element with low priority.
|
||||
// priorityQueue is based on heap structure.
|
||||
type priorityQueue struct {
|
||||
mu sync.Mutex // use sync.Mutex instead of sync.RWMutex for performance purpose.
|
||||
heap *priorityQueueHeap // the underlying queue items manager using heap.
|
||||
latestPriority *gtype.Int64 // latestPriority stores the most priority value of the heap, which is used to check if necessary to call the Pop of heap by Timer.
|
||||
mu sync.Mutex
|
||||
heap *priorityQueueHeap // the underlying queue items manager using heap.
|
||||
nextPriority *gtype.Int64 // nextPriority stores the next priority value of the heap, which is used to check if necessary to call the Pop of heap by Timer.
|
||||
}
|
||||
|
||||
// priorityQueueHeap is a heap manager, of which the underlying `array` is a array implementing a heap structure.
|
||||
@ -30,32 +30,23 @@ type priorityQueueHeap struct {
|
||||
|
||||
// priorityQueueItem stores the queue item which has a `priority` attribute to sort itself in heap.
|
||||
type priorityQueueItem struct {
|
||||
value interface{} // queue value.
|
||||
priority int64 // The lesser the `priority` value the higher priority of the `value`, for example: priority of 0 is greater than priority of 1.
|
||||
value interface{}
|
||||
priority int64
|
||||
}
|
||||
|
||||
// newPriorityQueue creates and returns a priority queue.
|
||||
func newPriorityQueue() *priorityQueue {
|
||||
queue := &priorityQueue{
|
||||
heap: &priorityQueueHeap{
|
||||
array: make([]priorityQueueItem, 0),
|
||||
},
|
||||
latestPriority: gtype.NewInt64(math.MaxInt64),
|
||||
heap: &priorityQueueHeap{array: make([]priorityQueueItem, 0)},
|
||||
nextPriority: gtype.NewInt64(math.MaxInt64),
|
||||
}
|
||||
heap.Init(queue.heap)
|
||||
return queue
|
||||
}
|
||||
|
||||
// Len retrieves and returns the length of the queue.
|
||||
func (q *priorityQueue) Len() int {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
return q.heap.Len()
|
||||
}
|
||||
|
||||
// LatestPriority retrieves and returns the minimum and the most priority value of the queue.
|
||||
func (q *priorityQueue) LatestPriority() int64 {
|
||||
return q.latestPriority.Val()
|
||||
// NextPriority retrieves and returns the minimum and the most priority value of the queue.
|
||||
func (q *priorityQueue) NextPriority() int64 {
|
||||
return q.nextPriority.Val()
|
||||
}
|
||||
|
||||
// Push pushes a value to the queue.
|
||||
@ -68,11 +59,12 @@ func (q *priorityQueue) Push(value interface{}, priority int64) {
|
||||
value: value,
|
||||
priority: priority,
|
||||
})
|
||||
|
||||
// Update the minimum priority using atomic operation.
|
||||
if priority < q.latestPriority.Val() {
|
||||
q.latestPriority.Set(priority)
|
||||
nextPriority := q.nextPriority.Val()
|
||||
if priority >= nextPriority {
|
||||
return
|
||||
}
|
||||
q.nextPriority.Set(priority)
|
||||
}
|
||||
|
||||
// Pop retrieves, removes and returns the most high priority value from the queue.
|
||||
@ -80,10 +72,12 @@ func (q *priorityQueue) Pop() interface{} {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
if v := heap.Pop(q.heap); v != nil {
|
||||
item := v.(priorityQueueItem)
|
||||
// Update the minimum priority using atomic operation.
|
||||
q.latestPriority.Set(item.priority)
|
||||
return item.value
|
||||
var nextPriority int64 = math.MaxInt64
|
||||
if len(q.heap.array) > 0 {
|
||||
nextPriority = q.heap.array[0].priority
|
||||
}
|
||||
q.nextPriority.Set(nextPriority)
|
||||
return v.(priorityQueueItem).value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ func (h *priorityQueueHeap) Len() int {
|
||||
}
|
||||
|
||||
// Less is used to implement the interface of sort.Interface.
|
||||
// The least one is placed to the top of the heap.
|
||||
func (h *priorityQueueHeap) Less(i, j int) bool {
|
||||
return h.array[i].priority < h.array[j].priority
|
||||
}
|
||||
|
||||
@ -23,8 +23,7 @@ func (t *Timer) loop() {
|
||||
switch t.status.Val() {
|
||||
case StatusRunning:
|
||||
// Timer proceeding.
|
||||
currentTimerTicks = t.ticks.Add(1)
|
||||
if currentTimerTicks >= t.queue.LatestPriority() {
|
||||
if currentTimerTicks = t.ticks.Add(1); currentTimerTicks >= t.queue.NextPriority() {
|
||||
t.proceed(currentTimerTicks)
|
||||
}
|
||||
|
||||
@ -40,7 +39,7 @@ func (t *Timer) loop() {
|
||||
}()
|
||||
}
|
||||
|
||||
// proceed proceeds the timer job checking and running logic.
|
||||
// proceed function proceeds the timer job checking and running logic.
|
||||
func (t *Timer) proceed(currentTimerTicks int64) {
|
||||
var (
|
||||
value interface{}
|
||||
@ -51,9 +50,9 @@ func (t *Timer) proceed(currentTimerTicks int64) {
|
||||
break
|
||||
}
|
||||
entry := value.(*Entry)
|
||||
// It checks if it meets the ticks requirement.
|
||||
// It checks if it meets the ticks' requirement.
|
||||
if jobNextTicks := entry.nextTicks.Val(); currentTimerTicks < jobNextTicks {
|
||||
// It push the job back if current ticks does not meet its running ticks requirement.
|
||||
// It pushes the job back if current ticks does not meet its running ticks requirement.
|
||||
t.queue.Push(entry, entry.nextTicks.Val())
|
||||
break
|
||||
}
|
||||
|
||||
@ -46,3 +46,38 @@ func TestTimer_Proceed(t *testing.T) {
|
||||
t.Assert(array.Len(), 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTimer_PriorityQueue(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
queue := newPriorityQueue()
|
||||
queue.Push(1, 1)
|
||||
queue.Push(4, 4)
|
||||
queue.Push(5, 5)
|
||||
queue.Push(2, 2)
|
||||
queue.Push(3, 3)
|
||||
t.Assert(queue.Pop(), 1)
|
||||
t.Assert(queue.Pop(), 2)
|
||||
t.Assert(queue.Pop(), 3)
|
||||
t.Assert(queue.Pop(), 4)
|
||||
t.Assert(queue.Pop(), 5)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTimer_PriorityQueue_FirstOneInArrayIsTheLeast(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
size = 1000000
|
||||
array = garray.NewIntArrayRange(0, size, 1)
|
||||
)
|
||||
array.Shuffle()
|
||||
queue := newPriorityQueue()
|
||||
array.Iterator(func(k int, v int) bool {
|
||||
queue.Push(v, int64(v))
|
||||
return true
|
||||
})
|
||||
for i := 0; i < size; i++ {
|
||||
t.Assert(queue.Pop(), i)
|
||||
t.Assert(queue.heap.array[0].priority, i+1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -53,7 +53,7 @@ type doConvertInput struct {
|
||||
Extra []interface{} // Extra values for implementing the converting.
|
||||
}
|
||||
|
||||
// doConvert does common used types converting.
|
||||
// doConvert does commonly used types converting.
|
||||
func doConvert(input doConvertInput) interface{} {
|
||||
switch input.ToTypeName {
|
||||
case "int":
|
||||
@ -366,7 +366,7 @@ func Runes(any interface{}) []rune {
|
||||
}
|
||||
|
||||
// String converts `any` to string.
|
||||
// It's most common used converting function.
|
||||
// It's most commonly used converting function.
|
||||
func String(any interface{}) string {
|
||||
if any == nil {
|
||||
return ""
|
||||
@ -459,7 +459,7 @@ func String(any interface{}) string {
|
||||
if kind == reflect.Ptr {
|
||||
return String(rv.Elem().Interface())
|
||||
}
|
||||
// Finally we use json.Marshal to convert.
|
||||
// Finally, we use json.Marshal to convert.
|
||||
if jsonContent, err := json.Marshal(value); err != nil {
|
||||
return fmt.Sprint(value)
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user