Skip to content

Commit 073e108

Browse files
authored
Support setting formatter function for specific type of params (#19)
* Support setting formatter function for specific type of params * Refactor readme * Add tests
1 parent 6f3c34c commit 073e108

5 files changed

+141
-1
lines changed

README.md

+21
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,27 @@ go get github.com/tiendc/go-validator
127127
}
128128
```
129129

130+
#### Custom error param formatter
131+
132+
```go
133+
errs := Validate(
134+
NumLT(&budget, 1000000).OnError(
135+
SetField("Budget", nil),
136+
),
137+
)
138+
139+
// e.BuildDetail() may produce message `Budget must be less than 1000000`,
140+
// but you may want a message like: `Budget must be less than 1,000,000`.
141+
// Let's use a custom formatter
142+
143+
errs := Validate(
144+
NumLT(&budget, 1000000).OnError(
145+
SetField("Budget", nil),
146+
SetNumParamFormatter(NewDecimalFormatFunc('.', ',', "%f")),
147+
),
148+
)
149+
```
150+
130151
## Contributing
131152

132153
- You are welcome to make pull requests for new functions and bug fixes.

error_formatter.go

+24
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ func NewTypedParamFormatter() TypedParamFormatter {
186186
// To attach this formatter to Error object:
187187
// - err.TypedParamFormatter().SetNumFormatFunc(NewDecimalNumFormatFunc())
188188
// - err.TypedParamFormatter().SetNumFormatFunc(NewDecimalNumFormatFunc("%.5f"))
189+
//
190+
// Deprecated: use NewDecimalFormatFunc instead
189191
func NewDecimalNumFormatFunc(floatFmt ...string) FormatFunc {
190192
return func(v reflect.Value) string {
191193
var s string
@@ -204,6 +206,28 @@ func NewDecimalNumFormatFunc(floatFmt ...string) FormatFunc {
204206
}
205207
}
206208

209+
// NewDecimalFormatFunc returns a FormatFunc which can format and group digits of decimal or integer
210+
// For example: '12345' -> '12,345', '12345.6789' -> '12,345.6789'
211+
// To attach this formatter to Error object:
212+
// - err.TypedParamFormatter().SetNumFormatFunc(NewDecimalFormatFunc('.', ',', "%.2f"))
213+
func NewDecimalFormatFunc(fractionSep, groupSep byte, floatFmt string) FormatFunc {
214+
return func(v reflect.Value) string {
215+
var s string
216+
// nolint: exhaustive
217+
switch v.Kind() {
218+
case reflect.Float64, reflect.Float32:
219+
fmtStr := floatFmt
220+
if fmtStr == "" {
221+
fmtStr = "%f"
222+
}
223+
s = fmt.Sprintf(fmtStr, v.Interface())
224+
default:
225+
s = fmt.Sprintf("%v", v.Interface())
226+
}
227+
return gofn.NumberFmtGroup(s, fractionSep, groupSep)
228+
}
229+
}
230+
207231
// NewSliceFormatFunc create a new func for formatting a slice
208232
// Sample arguments: leftWrap "[", rightWrap "]", elemSep ", "
209233
func NewSliceFormatFunc(

error_formatter_test.go

+9
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,15 @@ func Test_NewDecimalNumFormatFunc(t *testing.T) {
8484
assert.Equal(t, "1,234,567.12346", fmtFunc(reflect.ValueOf(1234567.1234567)))
8585
}
8686

87+
func Test_NewDecimalFormatFunc(t *testing.T) {
88+
fmtFunc := NewDecimalFormatFunc('.', ',', "")
89+
assert.Equal(t, "12,345", fmtFunc(reflect.ValueOf(12345)))
90+
assert.Equal(t, "1,234,567.123457", fmtFunc(reflect.ValueOf(1234567.1234567)))
91+
fmtFunc = NewDecimalFormatFunc('.', ',', "%.5f")
92+
assert.Equal(t, "12,345", fmtFunc(reflect.ValueOf(12345)))
93+
assert.Equal(t, "1,234,567.12346", fmtFunc(reflect.ValueOf(1234567.1234567)))
94+
}
95+
8796
func Test_errorBuildDetail(t *testing.T) {
8897
err := NewError().
8998
SetCustomKey("customKey").

errors.go

+58-1
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,8 @@ func (e *errorImpl) TypedParamFormatter() TypedParamFormatter {
197197
if e.paramsFormatter == nil {
198198
return nil
199199
}
200-
return e.paramsFormatter.(TypedParamFormatter) // nolint: forcetypeassert
200+
typedFmt, _ := e.paramsFormatter.(TypedParamFormatter)
201+
return typedFmt
201202
}
202203

203204
func (e *errorImpl) SetParamFormatter(formatter ErrorParamFormatter) Error {
@@ -287,3 +288,59 @@ func SetParamFormatter(formatter ErrorParamFormatter) ErrorMod {
287288
_ = err.SetParamFormatter(formatter)
288289
}
289290
}
291+
292+
func SetNumParamFormatter(formatFunc FormatFunc) ErrorMod {
293+
return func(err Error) {
294+
getTypedParamFormatterOrPanic(err).SetNumFormatFunc(formatFunc)
295+
}
296+
}
297+
298+
func SetStrParamFormatter(formatFunc FormatFunc) ErrorMod {
299+
return func(err Error) {
300+
getTypedParamFormatterOrPanic(err).SetStrFormatFunc(formatFunc)
301+
}
302+
}
303+
304+
func SetBoolParamFormatter(formatFunc FormatFunc) ErrorMod {
305+
return func(err Error) {
306+
getTypedParamFormatterOrPanic(err).SetBoolFormatFunc(formatFunc)
307+
}
308+
}
309+
310+
func SetSliceParamFormatter(formatFunc FormatFunc) ErrorMod {
311+
return func(err Error) {
312+
getTypedParamFormatterOrPanic(err).SetSliceFormatFunc(formatFunc)
313+
}
314+
}
315+
316+
func SetMapParamFormatter(formatFunc FormatFunc) ErrorMod {
317+
return func(err Error) {
318+
getTypedParamFormatterOrPanic(err).SetMapFormatFunc(formatFunc)
319+
}
320+
}
321+
322+
func SetStructParamFormatter(formatFunc FormatFunc) ErrorMod {
323+
return func(err Error) {
324+
getTypedParamFormatterOrPanic(err).SetStructFormatFunc(formatFunc)
325+
}
326+
}
327+
328+
func SetPtrParamFormatter(formatFunc FormatFunc) ErrorMod {
329+
return func(err Error) {
330+
getTypedParamFormatterOrPanic(err).SetPtrFormatFunc(formatFunc)
331+
}
332+
}
333+
334+
func SetCustomParamFormatter(formatFunc FormatFunc) ErrorMod {
335+
return func(err Error) {
336+
getTypedParamFormatterOrPanic(err).SetCustomFormatFunc(formatFunc)
337+
}
338+
}
339+
340+
func getTypedParamFormatterOrPanic(err Error) TypedParamFormatter {
341+
formatter := err.TypedParamFormatter()
342+
if formatter == nil {
343+
panic("error does not have a TypedParamFormatter attached")
344+
}
345+
return formatter
346+
}

errors_test.go

+29
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package validation
22

33
import (
4+
"reflect"
45
"testing"
56

67
"github.com/stretchr/testify/assert"
8+
"github.com/tiendc/gofn"
79
)
810

911
func Test_ErrorMod(t *testing.T) {
@@ -25,6 +27,33 @@ func Test_ErrorMod(t *testing.T) {
2527
SetParamFormatter(nil)(err)
2628
assert.Nil(t, err.ParamFormatter())
2729
assert.Nil(t, err.TypedParamFormatter())
30+
SetParamFormatter(NewTypedParamFormatter())(err)
31+
assert.NotNil(t, err.ParamFormatter())
32+
assert.NotNil(t, err.TypedParamFormatter())
33+
34+
SetNumParamFormatter(func(reflect.Value) string { return "num" })(err)
35+
SetStrParamFormatter(func(reflect.Value) string { return "str" })(err)
36+
SetBoolParamFormatter(func(reflect.Value) string { return "bool" })(err)
37+
SetSliceParamFormatter(func(reflect.Value) string { return "slice" })(err)
38+
SetMapParamFormatter(func(reflect.Value) string { return "map" })(err)
39+
SetStructParamFormatter(func(reflect.Value) string { return "struct" })(err)
40+
SetPtrParamFormatter(func(reflect.Value) string { return "ptr" })(err)
41+
SetCustomParamFormatter(func(reflect.Value) string { return "custom" })(err)
42+
assert.Equal(t, "num", err.TypedParamFormatter().Format("k", 123))
43+
assert.Equal(t, "str", err.TypedParamFormatter().Format("k", "123"))
44+
assert.Equal(t, "bool", err.TypedParamFormatter().Format("k", true))
45+
assert.Equal(t, "slice", err.TypedParamFormatter().Format("k", []int{123}))
46+
assert.Equal(t, "map", err.TypedParamFormatter().Format("k", map[string]any{}))
47+
assert.Equal(t, "struct", err.TypedParamFormatter().Format("k", struct{}{}))
48+
assert.Equal(t, "ptr", err.TypedParamFormatter().Format("k", gofn.New(123)))
49+
assert.Equal(t, "custom", err.TypedParamFormatter().Format("k", func() {}))
50+
51+
defer func() {
52+
e := recover()
53+
assert.Equal(t, "error does not have a TypedParamFormatter attached", e)
54+
}()
55+
SetParamFormatter(nil)(err)
56+
SetNumParamFormatter(func(reflect.Value) string { return "num" })(err)
2857
}
2958

3059
func Test_Field_Path(t *testing.T) {

0 commit comments

Comments
 (0)