mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-28 03:51:08 +01:00
v0.26.0
This commit is contained in:
@@ -43,12 +43,12 @@ type SerializedObject = map[string]any
|
||||
```go
|
||||
// For custom map unmarshaling logic
|
||||
type MapUnmarshaller interface {
|
||||
UnmarshalMap(m map[string]any) gperr.Error
|
||||
UnmarshalMap(m map[string]any) error
|
||||
}
|
||||
|
||||
// For custom validation logic
|
||||
type CustomValidator interface {
|
||||
Validate() gperr.Error
|
||||
Validate() error
|
||||
}
|
||||
```
|
||||
|
||||
@@ -56,16 +56,16 @@ type CustomValidator interface {
|
||||
|
||||
```go
|
||||
// Generic unmarshal with pluggable format handler
|
||||
func UnmarshalValidate[T any](data []byte, target *T, unmarshaler unmarshalFunc, interceptFns ...interceptFunc) gperr.Error
|
||||
func UnmarshalValidate[T any](data []byte, target *T, unmarshaler unmarshalFunc, interceptFns ...interceptFunc) error
|
||||
|
||||
// Read from io.Reader with format decoder
|
||||
func UnmarshalValidateReader[T any](reader io.Reader, target *T, newDecoder newDecoderFunc, interceptFns ...interceptFunc) gperr.Error
|
||||
func UnmarshalValidateReader[T any](reader io.Reader, target *T, newDecoder newDecoderFunc, interceptFns ...interceptFunc) error
|
||||
|
||||
// Direct map deserialization
|
||||
func MapUnmarshalValidate(src SerializedObject, dst any) gperr.Error
|
||||
func MapUnmarshalValidate(src SerializedObject, dst any) error
|
||||
|
||||
// To xsync.Map with pluggable format handler
|
||||
func UnmarshalValidateXSync[V any](data []byte, unmarshaler unmarshalFunc, interceptFns ...interceptFunc) (*xsync.Map[string, V], gperr.Error)
|
||||
func UnmarshalValidateXSync[V any](data []byte, unmarshaler unmarshalFunc, interceptFns ...interceptFunc) (*xsync.Map[string, V], error)
|
||||
```
|
||||
|
||||
### File I/O Functions
|
||||
@@ -82,23 +82,23 @@ func LoadFileIfExist[T any](path string, dst *T, unmarshaler unmarshalFunc) erro
|
||||
|
||||
```go
|
||||
// Convert any value to target reflect.Value
|
||||
func Convert(src reflect.Value, dst reflect.Value, checkValidateTag bool) gperr.Error
|
||||
func Convert(src reflect.Value, dst reflect.Value, checkValidateTag bool) error
|
||||
|
||||
// String to target type conversion
|
||||
func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gperr.Error)
|
||||
func ConvertString(src string, dst reflect.Value) (convertible bool, convErr error)
|
||||
```
|
||||
|
||||
### Validation Functions
|
||||
|
||||
```go
|
||||
// Validate using struct tags
|
||||
func ValidateWithFieldTags(s any) gperr.Error
|
||||
func ValidateWithFieldTags(s any) error
|
||||
|
||||
// Register custom validator
|
||||
func MustRegisterValidation(tag string, fn validator.Func)
|
||||
|
||||
// Validate using CustomValidator interface
|
||||
func ValidateWithCustomValidator(v reflect.Value) gperr.Error
|
||||
func ValidateWithCustomValidator(v reflect.Value) error
|
||||
|
||||
// Get underlying validator
|
||||
func Validator() *validator.Validate
|
||||
@@ -301,9 +301,9 @@ type Config struct {
|
||||
URL string `json:"url" validate:"required"`
|
||||
}
|
||||
|
||||
func (c *Config) Validate() gperr.Error {
|
||||
func (c *Config) Validate() error {
|
||||
if !strings.HasPrefix(c.URL, "https://") {
|
||||
return gperr.New("url must use https").Subject("url")
|
||||
return errors.New("url must use https")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@ package serialization_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/yusing/godoxy/internal/serialization"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
type TestStruct struct {
|
||||
@@ -14,18 +15,17 @@ type TestStruct struct {
|
||||
Value2 int `json:"value2"`
|
||||
}
|
||||
|
||||
func (t *TestStruct) Validate() gperr.Error {
|
||||
func (t *TestStruct) Validate() error {
|
||||
if t.Value == "" {
|
||||
return gperr.New("value is required")
|
||||
return errors.New("value is required")
|
||||
}
|
||||
if t.Value2 != 0 && (t.Value2 < 5 || t.Value2 > 10) {
|
||||
return gperr.New("value2 must be between 5 and 10")
|
||||
return errors.New("value2 must be between 5 and 10")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestGinBinding(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
@@ -40,7 +40,7 @@ func TestGinBinding(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var dst TestStruct
|
||||
body := bytes.NewBufferString(tt.input)
|
||||
req := httptest.NewRequest("POST", "/", body)
|
||||
req := httptest.NewRequest(http.MethodPost, "/", body)
|
||||
err := serialization.GinJSONBinding{}.Bind(req, &dst)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("%s: Bind() error = %v, wantErr %v", tt.name, err, tt.wantErr)
|
||||
|
||||
@@ -15,8 +15,10 @@ func NewSubstituteEnvReader(reader io.Reader) *SubstituteEnvReader {
|
||||
return &SubstituteEnvReader{reader: reader}
|
||||
}
|
||||
|
||||
const peekSize = 4096
|
||||
const maxVarNameLength = 256
|
||||
const (
|
||||
peekSize = 4096
|
||||
maxVarNameLength = 256
|
||||
)
|
||||
|
||||
func (r *SubstituteEnvReader) Read(p []byte) (n int, err error) {
|
||||
// Return buffered data first
|
||||
@@ -66,6 +68,7 @@ func (r *SubstituteEnvReader) Read(p []byte) (n int, err error) {
|
||||
if nMore > 0 {
|
||||
incomplete = append(incomplete, more[:nMore]...)
|
||||
// Check if pattern is now complete
|
||||
//nolint:modernize
|
||||
if idx := bytes.IndexByte(incomplete, '}'); idx >= 0 {
|
||||
// Pattern complete, append the rest back to chunk
|
||||
chunk = append(chunk, incomplete...)
|
||||
|
||||
@@ -2,8 +2,8 @@ package serialization
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -11,17 +11,9 @@ import (
|
||||
// setupEnv sets up environment variables for benchmarks
|
||||
func setupEnv(b *testing.B) {
|
||||
b.Helper()
|
||||
os.Setenv("BENCH_VAR", "benchmark_value")
|
||||
os.Setenv("BENCH_VAR_2", "second_value")
|
||||
os.Setenv("BENCH_VAR_3", "third_value")
|
||||
}
|
||||
|
||||
// cleanupEnv cleans up environment variables after benchmarks
|
||||
func cleanupEnv(b *testing.B) {
|
||||
b.Helper()
|
||||
os.Unsetenv("BENCH_VAR")
|
||||
os.Unsetenv("BENCH_VAR_2")
|
||||
os.Unsetenv("BENCH_VAR_3")
|
||||
b.Setenv("BENCH_VAR", "benchmark_value")
|
||||
b.Setenv("BENCH_VAR_2", "second_value")
|
||||
b.Setenv("BENCH_VAR_3", "third_value")
|
||||
}
|
||||
|
||||
// BenchmarkSubstituteEnvReader_NoSubstitution benchmarks reading without any env substitutions
|
||||
@@ -44,7 +36,6 @@ data: some content here
|
||||
// BenchmarkSubstituteEnvReader_SingleSubstitution benchmarks reading with a single env substitution
|
||||
func BenchmarkSubstituteEnvReader_SingleSubstitution(b *testing.B) {
|
||||
setupEnv(b)
|
||||
defer cleanupEnv(b)
|
||||
|
||||
r := strings.NewReader(`key: ${BENCH_VAR}
|
||||
`)
|
||||
@@ -62,7 +53,6 @@ func BenchmarkSubstituteEnvReader_SingleSubstitution(b *testing.B) {
|
||||
// BenchmarkSubstituteEnvReader_MultipleSubstitutions benchmarks reading with multiple env substitutions
|
||||
func BenchmarkSubstituteEnvReader_MultipleSubstitutions(b *testing.B) {
|
||||
setupEnv(b)
|
||||
defer cleanupEnv(b)
|
||||
|
||||
r := strings.NewReader(`key1: ${BENCH_VAR}
|
||||
key2: ${BENCH_VAR_2}
|
||||
@@ -96,7 +86,6 @@ func BenchmarkSubstituteEnvReader_LargeInput_NoSubstitution(b *testing.B) {
|
||||
// BenchmarkSubstituteEnvReader_LargeInput_WithSubstitutions benchmarks large input with scattered substitutions
|
||||
func BenchmarkSubstituteEnvReader_LargeInput_WithSubstitutions(b *testing.B) {
|
||||
setupEnv(b)
|
||||
defer cleanupEnv(b)
|
||||
|
||||
var builder bytes.Buffer
|
||||
for range 100 {
|
||||
@@ -118,7 +107,6 @@ func BenchmarkSubstituteEnvReader_LargeInput_WithSubstitutions(b *testing.B) {
|
||||
// BenchmarkSubstituteEnvReader_SmallBuffer benchmarks reading with a small buffer size
|
||||
func BenchmarkSubstituteEnvReader_SmallBuffer(b *testing.B) {
|
||||
setupEnv(b)
|
||||
defer cleanupEnv(b)
|
||||
|
||||
r := strings.NewReader(`key: ${BENCH_VAR} and some more content here`)
|
||||
buf := make([]byte, 16)
|
||||
@@ -127,7 +115,7 @@ func BenchmarkSubstituteEnvReader_SmallBuffer(b *testing.B) {
|
||||
reader := NewSubstituteEnvReader(r)
|
||||
for {
|
||||
_, err := reader.Read(buf)
|
||||
if err == io.EOF {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
@@ -141,7 +129,6 @@ func BenchmarkSubstituteEnvReader_SmallBuffer(b *testing.B) {
|
||||
// BenchmarkSubstituteEnvReader_YAMLConfig benchmarks a realistic YAML config scenario
|
||||
func BenchmarkSubstituteEnvReader_YAMLConfig(b *testing.B) {
|
||||
setupEnv(b)
|
||||
defer cleanupEnv(b)
|
||||
|
||||
r := strings.NewReader(`database:
|
||||
host: ${BENCH_VAR}
|
||||
@@ -170,7 +157,6 @@ server:
|
||||
// BenchmarkSubstituteEnvReader_BoundaryPattern benchmarks patterns at buffer boundaries (4096 bytes)
|
||||
func BenchmarkSubstituteEnvReader_BoundaryPattern(b *testing.B) {
|
||||
setupEnv(b)
|
||||
defer cleanupEnv(b)
|
||||
|
||||
// Pattern exactly at 4090 bytes, with ${VAR} crossing the 4096 boundary
|
||||
prefix := strings.Repeat("x", 4090)
|
||||
@@ -189,7 +175,6 @@ func BenchmarkSubstituteEnvReader_BoundaryPattern(b *testing.B) {
|
||||
// BenchmarkSubstituteEnvReader_MultipleBoundaries benchmarks multiple patterns crossing boundaries
|
||||
func BenchmarkSubstituteEnvReader_MultipleBoundaries(b *testing.B) {
|
||||
setupEnv(b)
|
||||
defer cleanupEnv(b)
|
||||
|
||||
var builder bytes.Buffer
|
||||
for range 10 {
|
||||
@@ -210,8 +195,7 @@ func BenchmarkSubstituteEnvReader_MultipleBoundaries(b *testing.B) {
|
||||
|
||||
// BenchmarkSubstituteEnvReader_SpecialChars benchmarks substitution with special characters
|
||||
func BenchmarkSubstituteEnvReader_SpecialChars(b *testing.B) {
|
||||
os.Setenv("SPECIAL_BENCH_VAR", `value with "quotes" and \backslash\`)
|
||||
defer os.Unsetenv("SPECIAL_BENCH_VAR")
|
||||
b.Setenv("SPECIAL_BENCH_VAR", `value with "quotes" and \backslash\`)
|
||||
|
||||
r := strings.NewReader(`key: ${SPECIAL_BENCH_VAR}
|
||||
`)
|
||||
@@ -228,8 +212,7 @@ func BenchmarkSubstituteEnvReader_SpecialChars(b *testing.B) {
|
||||
|
||||
// BenchmarkSubstituteEnvReader_EmptyValue benchmarks substitution with empty value
|
||||
func BenchmarkSubstituteEnvReader_EmptyValue(b *testing.B) {
|
||||
os.Setenv("EMPTY_BENCH_VAR", "")
|
||||
defer os.Unsetenv("EMPTY_BENCH_VAR")
|
||||
b.Setenv("EMPTY_BENCH_VAR", "")
|
||||
|
||||
r := strings.NewReader(`key: ${EMPTY_BENCH_VAR}
|
||||
`)
|
||||
@@ -246,8 +229,7 @@ func BenchmarkSubstituteEnvReader_EmptyValue(b *testing.B) {
|
||||
|
||||
// BenchmarkSubstituteEnvReader_DollarWithoutBrace benchmarks $ without following {
|
||||
func BenchmarkSubstituteEnvReader_DollarWithoutBrace(b *testing.B) {
|
||||
os.Setenv("BENCH_VAR", "benchmark_value")
|
||||
defer os.Unsetenv("BENCH_VAR")
|
||||
b.Setenv("BENCH_VAR", "benchmark_value")
|
||||
|
||||
r := strings.NewReader(`price: $100 and $200 for ${BENCH_VAR}`)
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ package serialization
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -11,8 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func TestSubstituteEnvReader_Basic(t *testing.T) {
|
||||
os.Setenv("TEST_VAR", "hello")
|
||||
defer os.Unsetenv("TEST_VAR")
|
||||
t.Setenv("TEST_VAR", "hello")
|
||||
|
||||
input := []byte(`key: ${TEST_VAR}`)
|
||||
reader := NewSubstituteEnvReader(bytes.NewReader(input))
|
||||
@@ -23,10 +22,8 @@ func TestSubstituteEnvReader_Basic(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSubstituteEnvReader_Multiple(t *testing.T) {
|
||||
os.Setenv("VAR1", "first")
|
||||
os.Setenv("VAR2", "second")
|
||||
defer os.Unsetenv("VAR1")
|
||||
defer os.Unsetenv("VAR2")
|
||||
t.Setenv("VAR1", "first")
|
||||
t.Setenv("VAR2", "second")
|
||||
|
||||
input := []byte(`a: ${VAR1}, b: ${VAR2}`)
|
||||
reader := NewSubstituteEnvReader(bytes.NewReader(input))
|
||||
@@ -46,8 +43,6 @@ func TestSubstituteEnvReader_NoSubstitution(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSubstituteEnvReader_UnsetEnvError(t *testing.T) {
|
||||
os.Unsetenv("UNSET_VAR_FOR_TEST")
|
||||
|
||||
input := []byte(`key: ${UNSET_VAR_FOR_TEST}`)
|
||||
reader := NewSubstituteEnvReader(bytes.NewReader(input))
|
||||
|
||||
@@ -57,8 +52,7 @@ func TestSubstituteEnvReader_UnsetEnvError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSubstituteEnvReader_SmallBuffer(t *testing.T) {
|
||||
os.Setenv("SMALL_BUF_VAR", "value")
|
||||
defer os.Unsetenv("SMALL_BUF_VAR")
|
||||
t.Setenv("SMALL_BUF_VAR", "value")
|
||||
|
||||
input := []byte(`key: ${SMALL_BUF_VAR}`)
|
||||
reader := NewSubstituteEnvReader(bytes.NewReader(input))
|
||||
@@ -70,7 +64,7 @@ func TestSubstituteEnvReader_SmallBuffer(t *testing.T) {
|
||||
if n > 0 {
|
||||
result = append(result, buf[:n]...)
|
||||
}
|
||||
if err == io.EOF {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
require.NoError(t, err)
|
||||
@@ -79,8 +73,7 @@ func TestSubstituteEnvReader_SmallBuffer(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSubstituteEnvReader_SpecialChars(t *testing.T) {
|
||||
os.Setenv("SPECIAL_VAR", `hello "world" \n`)
|
||||
defer os.Unsetenv("SPECIAL_VAR")
|
||||
t.Setenv("SPECIAL_VAR", `hello "world" \n`)
|
||||
|
||||
input := []byte(`key: ${SPECIAL_VAR}`)
|
||||
reader := NewSubstituteEnvReader(bytes.NewReader(input))
|
||||
@@ -91,8 +84,7 @@ func TestSubstituteEnvReader_SpecialChars(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSubstituteEnvReader_EmptyValue(t *testing.T) {
|
||||
os.Setenv("EMPTY_VAR", "")
|
||||
defer os.Unsetenv("EMPTY_VAR")
|
||||
t.Setenv("EMPTY_VAR", "")
|
||||
|
||||
input := []byte(`key: ${EMPTY_VAR}`)
|
||||
reader := NewSubstituteEnvReader(bytes.NewReader(input))
|
||||
@@ -103,8 +95,7 @@ func TestSubstituteEnvReader_EmptyValue(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSubstituteEnvReader_LargeInput(t *testing.T) {
|
||||
os.Setenv("LARGE_VAR", "replaced")
|
||||
defer os.Unsetenv("LARGE_VAR")
|
||||
t.Setenv("LARGE_VAR", "replaced")
|
||||
|
||||
prefix := strings.Repeat("x", 5000)
|
||||
suffix := strings.Repeat("y", 5000)
|
||||
@@ -119,8 +110,7 @@ func TestSubstituteEnvReader_LargeInput(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSubstituteEnvReader_PatternAtBoundary(t *testing.T) {
|
||||
os.Setenv("BOUNDARY_VAR", "boundary_value")
|
||||
defer os.Unsetenv("BOUNDARY_VAR")
|
||||
t.Setenv("BOUNDARY_VAR", "boundary_value")
|
||||
|
||||
prefix := strings.Repeat("a", 4090)
|
||||
input := []byte(prefix + "${BOUNDARY_VAR}")
|
||||
@@ -134,10 +124,8 @@ func TestSubstituteEnvReader_PatternAtBoundary(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSubstituteEnvReader_MultiplePatternsBoundary(t *testing.T) {
|
||||
os.Setenv("VAR_A", "aaa")
|
||||
os.Setenv("VAR_B", "bbb")
|
||||
defer os.Unsetenv("VAR_A")
|
||||
defer os.Unsetenv("VAR_B")
|
||||
t.Setenv("VAR_A", "aaa")
|
||||
t.Setenv("VAR_B", "bbb")
|
||||
|
||||
prefix := strings.Repeat("x", 4090)
|
||||
input := []byte(prefix + "${VAR_A} middle ${VAR_B}")
|
||||
@@ -151,12 +139,9 @@ func TestSubstituteEnvReader_MultiplePatternsBoundary(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSubstituteEnvReader_YAMLConfig(t *testing.T) {
|
||||
os.Setenv("DB_HOST", "localhost")
|
||||
os.Setenv("DB_PORT", "5432")
|
||||
os.Setenv("DB_PASSWORD", "secret123")
|
||||
defer os.Unsetenv("DB_HOST")
|
||||
defer os.Unsetenv("DB_PORT")
|
||||
defer os.Unsetenv("DB_PASSWORD")
|
||||
t.Setenv("DB_HOST", "localhost")
|
||||
t.Setenv("DB_PORT", "5432")
|
||||
t.Setenv("DB_PASSWORD", "secret123")
|
||||
|
||||
input := []byte(`database:
|
||||
host: ${DB_HOST}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package serialization
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
@@ -11,7 +13,6 @@ import (
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/puzpuzpuz/xsync/v4"
|
||||
@@ -33,22 +34,22 @@ func ToSerializedObject[VT any](m map[string]VT) SerializedObject {
|
||||
}
|
||||
|
||||
func init() {
|
||||
strutils.SetJSONMarshaler(sonic.Marshal)
|
||||
strutils.SetJSONUnmarshaler(sonic.Unmarshal)
|
||||
strutils.SetJSONMarshaler(json.Marshal)
|
||||
strutils.SetJSONUnmarshaler(json.Unmarshal)
|
||||
strutils.SetYAMLMarshaler(yaml.Marshal)
|
||||
strutils.SetYAMLUnmarshaler(yaml.Unmarshal)
|
||||
}
|
||||
|
||||
type MapUnmarshaller interface {
|
||||
UnmarshalMap(m map[string]any) gperr.Error
|
||||
UnmarshalMap(m map[string]any) error
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidType = gperr.New("invalid type")
|
||||
ErrNilValue = gperr.New("nil")
|
||||
ErrUnsettable = gperr.New("unsettable")
|
||||
ErrUnsupportedConversion = gperr.New("unsupported conversion")
|
||||
ErrUnknownField = gperr.New("unknown field")
|
||||
ErrInvalidType = errors.New("invalid type")
|
||||
ErrNilValue = errors.New("nil")
|
||||
ErrUnsettable = errors.New("unsettable")
|
||||
ErrUnsupportedConversion = errors.New("unsupported conversion")
|
||||
ErrUnknownField = errors.New("unknown field")
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -86,11 +87,11 @@ func initPtr(dst reflect.Value) {
|
||||
}
|
||||
}
|
||||
|
||||
// Validate performs struct validation using go-playground/validator tags.
|
||||
// ValidateWithFieldTags performs struct validation using go-playground/validator tags.
|
||||
//
|
||||
// It collects all validation errors and returns them as a single error.
|
||||
// Field names in errors are prefixed with their namespace (e.g., "User.Email").
|
||||
func ValidateWithFieldTags(s any) gperr.Error {
|
||||
func ValidateWithFieldTags(s any) error {
|
||||
var errs gperr.Builder
|
||||
err := validate.Struct(s)
|
||||
var valErrs validator.ValidationErrors
|
||||
@@ -103,15 +104,16 @@ func ValidateWithFieldTags(s any) gperr.Error {
|
||||
if detail != "required" {
|
||||
detail = "require " + strconv.Quote(detail)
|
||||
}
|
||||
errs.Add(ErrValidationError.
|
||||
Subject(e.Namespace()).
|
||||
Withf(detail))
|
||||
errs.Add(gperr.PrependSubject(ErrValidationError, e.Namespace()).
|
||||
Withf("%s", detail))
|
||||
}
|
||||
}
|
||||
return errs.Error()
|
||||
}
|
||||
|
||||
func dive(dst reflect.Value) (v reflect.Value, t reflect.Type, err gperr.Error) {
|
||||
// dive recursively dives into the nested pointers of the dst.
|
||||
// dst value pointer must be valid (satisfies reflect.Value.IsValid()).
|
||||
func dive(dst reflect.Value) (v reflect.Value, t reflect.Type) {
|
||||
dstT := dst.Type()
|
||||
for {
|
||||
switch dstT.Kind() {
|
||||
@@ -119,7 +121,7 @@ func dive(dst reflect.Value) (v reflect.Value, t reflect.Type, err gperr.Error)
|
||||
dst = dst.Elem()
|
||||
dstT = dstT.Elem()
|
||||
default:
|
||||
return dst, dstT, nil
|
||||
return dst, dstT
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -276,32 +278,26 @@ func initTypeKeyFieldIndexesMap(t reflect.Type) typeInfo {
|
||||
// If the target value is a map[string]any the SerializedObject will be deserialized into the map.
|
||||
//
|
||||
// The function returns an error if the target value is not a struct or a map[string]any, or if there is an error during deserialization.
|
||||
func MapUnmarshalValidate(src SerializedObject, dst any) (err gperr.Error) {
|
||||
func MapUnmarshalValidate(src SerializedObject, dst any) error {
|
||||
return mapUnmarshalValidate(src, reflect.ValueOf(dst), true)
|
||||
}
|
||||
|
||||
func mapUnmarshalValidate(src SerializedObject, dstV reflect.Value, checkValidateTag bool) (err gperr.Error) {
|
||||
func mapUnmarshalValidate(src SerializedObject, dstV reflect.Value, checkValidateTag bool) (err error) {
|
||||
dstT := dstV.Type()
|
||||
|
||||
if src != nil && dstT.Implements(mapUnmarshalerType) {
|
||||
dstV, _, err = dive(dstV)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dstV, _ = dive(dstV)
|
||||
return dstV.Addr().Interface().(MapUnmarshaller).UnmarshalMap(src)
|
||||
}
|
||||
|
||||
dstV, dstT, err = dive(dstV)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dstV, dstT = dive(dstV)
|
||||
|
||||
if src == nil {
|
||||
if dstV.CanSet() {
|
||||
dstV.SetZero()
|
||||
return nil
|
||||
}
|
||||
return gperr.Errorf("deserialize: src is %w and dst is not settable", ErrNilValue)
|
||||
return fmt.Errorf("deserialize: src is %w and dst is not settable", ErrNilValue)
|
||||
}
|
||||
|
||||
// convert data fields to lower no-snake
|
||||
@@ -317,10 +313,10 @@ func mapUnmarshalValidate(src SerializedObject, dstV reflect.Value, checkValidat
|
||||
if field, ok := info.getField(dstV, k); ok {
|
||||
err := Convert(reflect.ValueOf(v), field, checkValidateTag)
|
||||
if err != nil {
|
||||
errs.Add(err.Subject(k))
|
||||
errs.AddSubject(err, k)
|
||||
}
|
||||
} else {
|
||||
errs.Add(ErrUnknownField.Subject(k).With(gperr.DoYouMeanField(k, info.fieldNames)))
|
||||
errs.Add(gperr.PrependSubject(ErrUnknownField, k).With(gperr.DoYouMeanField(k, info.fieldNames)))
|
||||
}
|
||||
}
|
||||
if info.hasValidateTag && checkValidateTag {
|
||||
@@ -333,23 +329,23 @@ func mapUnmarshalValidate(src SerializedObject, dstV reflect.Value, checkValidat
|
||||
case reflect.Map:
|
||||
if dstV.IsNil() {
|
||||
if !dstV.CanSet() {
|
||||
return gperr.Errorf("dive: dst is %w and is not settable", ErrNilValue)
|
||||
return fmt.Errorf("dive: dst is %w and is not settable", ErrNilValue)
|
||||
}
|
||||
gi.ReflectInitMap(dstV, len(src))
|
||||
}
|
||||
if dstT.Key().Kind() != reflect.String {
|
||||
return gperr.Errorf("deserialize: %w for map of non string keys (map of %s)", ErrUnsupportedConversion, dstT.Elem().String())
|
||||
return fmt.Errorf("deserialize: %w for map of non string keys (map of %s)", ErrUnsupportedConversion, dstT.Elem().String())
|
||||
}
|
||||
// ?: should we clear the map?
|
||||
for k, v := range src {
|
||||
elem := gi.ReflectStrMapAssign(dstV, k)
|
||||
err := Convert(reflect.ValueOf(v), elem, true)
|
||||
if err != nil {
|
||||
errs.Add(err.Subject(k))
|
||||
errs.AddSubject(err, k)
|
||||
continue
|
||||
}
|
||||
if err := ValidateWithCustomValidator(elem); err != nil {
|
||||
errs.Add(err.Subject(k))
|
||||
errs.AddSubject(err, k)
|
||||
}
|
||||
}
|
||||
if err := ValidateWithCustomValidator(dstV); err != nil {
|
||||
@@ -357,7 +353,7 @@ func mapUnmarshalValidate(src SerializedObject, dstV reflect.Value, checkValidat
|
||||
}
|
||||
return errs.Error()
|
||||
default:
|
||||
return ErrUnsupportedConversion.Subject("mapping to " + dstT.String() + " ")
|
||||
return fmt.Errorf("deserialize: %w for mapping to %s", ErrUnsupportedConversion, dstT)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,14 +369,14 @@ func mapUnmarshalValidate(src SerializedObject, dstV reflect.Value, checkValidat
|
||||
//
|
||||
// Returns:
|
||||
// - error: the error occurred during conversion, or nil if no error occurred.
|
||||
func Convert(src reflect.Value, dst reflect.Value, checkValidateTag bool) gperr.Error {
|
||||
func Convert(src reflect.Value, dst reflect.Value, checkValidateTag bool) error {
|
||||
if !dst.IsValid() {
|
||||
return gperr.Errorf("convert: dst is %w", ErrNilValue)
|
||||
return fmt.Errorf("convert: dst is %w", ErrNilValue)
|
||||
}
|
||||
|
||||
if (src.Kind() == reflect.Pointer && src.IsNil()) || !src.IsValid() {
|
||||
if !dst.CanSet() {
|
||||
return gperr.Errorf("convert: src is %w", ErrNilValue)
|
||||
return fmt.Errorf("convert: src is %w", ErrNilValue)
|
||||
}
|
||||
dst.SetZero()
|
||||
return nil
|
||||
@@ -388,7 +384,7 @@ func Convert(src reflect.Value, dst reflect.Value, checkValidateTag bool) gperr.
|
||||
|
||||
if src.IsZero() {
|
||||
if !dst.CanSet() {
|
||||
return gperr.Errorf("convert: src is %w", ErrNilValue)
|
||||
return fmt.Errorf("convert: src is %w", ErrNilValue)
|
||||
}
|
||||
switch dst.Kind() {
|
||||
case reflect.Pointer:
|
||||
@@ -410,7 +406,7 @@ func Convert(src reflect.Value, dst reflect.Value, checkValidateTag bool) gperr.
|
||||
if dst.Kind() == reflect.Pointer {
|
||||
if dst.IsNil() {
|
||||
if !dst.CanSet() {
|
||||
return ErrUnsettable.Subject(dstT.String())
|
||||
return fmt.Errorf("convert: dst is %w", ErrUnsettable)
|
||||
}
|
||||
initPtr(dst)
|
||||
}
|
||||
@@ -423,13 +419,13 @@ func Convert(src reflect.Value, dst reflect.Value, checkValidateTag bool) gperr.
|
||||
switch {
|
||||
case srcT == dstT, srcT.AssignableTo(dstT):
|
||||
if !dst.CanSet() {
|
||||
return ErrUnsettable.Subject(dstT.String())
|
||||
return fmt.Errorf("convert: dst is %w", ErrUnsettable)
|
||||
}
|
||||
dst.Set(src)
|
||||
return nil
|
||||
case srcKind == reflect.String:
|
||||
if !dst.CanSet() {
|
||||
return ErrUnsettable.Subject(dstT.String())
|
||||
return fmt.Errorf("convert: dst is %w", ErrUnsettable)
|
||||
}
|
||||
if convertible, err := ConvertString(src.String(), dst); convertible {
|
||||
return err
|
||||
@@ -451,14 +447,14 @@ func Convert(src reflect.Value, dst reflect.Value, checkValidateTag bool) gperr.
|
||||
}
|
||||
obj, ok := src.Interface().(SerializedObject)
|
||||
if !ok {
|
||||
return ErrUnsupportedConversion.Subject(dstT.String() + " to " + srcT.String())
|
||||
return fmt.Errorf("convert: %w from %s to %s", ErrUnsupportedConversion, srcT, dstT)
|
||||
}
|
||||
return mapUnmarshalValidate(obj, dst.Addr(), checkValidateTag)
|
||||
case srcKind == reflect.Slice: // slice to slice
|
||||
return ConvertSlice(src, dst, checkValidateTag)
|
||||
}
|
||||
|
||||
return ErrUnsupportedConversion.Subjectf("%s to %s", srcT, dstT)
|
||||
return fmt.Errorf("convert: %w for %s to %s", ErrUnsupportedConversion, srcT, dstT)
|
||||
}
|
||||
|
||||
// ConvertSlice converts a source slice to a destination slice.
|
||||
@@ -468,17 +464,17 @@ func Convert(src reflect.Value, dst reflect.Value, checkValidateTag bool) gperr.
|
||||
// - The destination slice is initialized with the source length.
|
||||
// - On error, the destination slice is truncated to the number of
|
||||
// successfully converted elements.
|
||||
func ConvertSlice(src reflect.Value, dst reflect.Value, checkValidateTag bool) gperr.Error {
|
||||
func ConvertSlice(src reflect.Value, dst reflect.Value, checkValidateTag bool) error {
|
||||
if dst.Kind() == reflect.Pointer {
|
||||
if dst.IsNil() && !dst.CanSet() {
|
||||
return ErrNilValue
|
||||
return fmt.Errorf("convert: dst is %w", ErrNilValue)
|
||||
}
|
||||
initPtr(dst)
|
||||
dst = dst.Elem()
|
||||
}
|
||||
|
||||
if !dst.CanSet() {
|
||||
return ErrUnsettable.Subject(dst.Type().String())
|
||||
return fmt.Errorf("convert: dst is %w", ErrUnsettable)
|
||||
}
|
||||
|
||||
if src.Kind() != reflect.Slice {
|
||||
@@ -491,7 +487,7 @@ func ConvertSlice(src reflect.Value, dst reflect.Value, checkValidateTag bool) g
|
||||
return nil
|
||||
}
|
||||
if dst.Kind() != reflect.Slice {
|
||||
return ErrUnsupportedConversion.Subjectf("%s to %s", dst.Type(), src.Type())
|
||||
return fmt.Errorf("convert: %w for %s to %s", ErrUnsupportedConversion, dst.Type(), src.Type())
|
||||
}
|
||||
|
||||
var sliceErrs gperr.Builder
|
||||
@@ -500,7 +496,7 @@ func ConvertSlice(src reflect.Value, dst reflect.Value, checkValidateTag bool) g
|
||||
for j := range srcLen {
|
||||
err := Convert(src.Index(j), dst.Index(numValid), checkValidateTag)
|
||||
if err != nil {
|
||||
sliceErrs.Add(err.Subjectf("[%d]", j))
|
||||
sliceErrs.AddSubjectf(err, "[%d]", j)
|
||||
continue
|
||||
}
|
||||
numValid++
|
||||
@@ -526,8 +522,7 @@ func ConvertSlice(src reflect.Value, dst reflect.Value, checkValidateTag bool) g
|
||||
// - If the destination implements the Parser interface, it is used for conversion.
|
||||
// - Returns true if conversion was handled (even with error), false if
|
||||
// conversion is unsupported.
|
||||
func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gperr.Error) {
|
||||
convertible = true
|
||||
func ConvertString(src string, dst reflect.Value) (convertible bool, convErr error) {
|
||||
dstT := dst.Type()
|
||||
if dst.Kind() == reflect.Pointer {
|
||||
if dst.IsNil() {
|
||||
@@ -555,14 +550,14 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gpe
|
||||
// check if (*T).Convertor is implemented
|
||||
if addr := dst.Addr(); addr.Type().Implements(reflect.TypeFor[strutils.Parser]()) {
|
||||
parser := addr.Interface().(strutils.Parser)
|
||||
return true, gperr.Wrap(parser.Parse(src))
|
||||
return true, parser.Parse(src)
|
||||
}
|
||||
|
||||
switch dstT {
|
||||
case reflect.TypeFor[time.Duration]():
|
||||
d, err := time.ParseDuration(src)
|
||||
if err != nil {
|
||||
return true, gperr.Wrap(err)
|
||||
return true, err
|
||||
}
|
||||
gi.ReflectValueSet(dst, d)
|
||||
return true, nil
|
||||
@@ -572,7 +567,7 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gpe
|
||||
if gi.ReflectIsNumeric(dst) || dst.Kind() == reflect.Bool {
|
||||
err := gi.ReflectStrToNumBool(dst, src)
|
||||
if err != nil {
|
||||
return true, gperr.Wrap(err)
|
||||
return true, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
@@ -602,14 +597,14 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gpe
|
||||
sl := []any{}
|
||||
err := yaml.Unmarshal(unsafe.Slice(unsafe.StringData(src), len(src)), &sl)
|
||||
if err != nil {
|
||||
return true, gperr.Wrap(err)
|
||||
return true, err
|
||||
}
|
||||
return true, ConvertSlice(reflect.ValueOf(sl), dst, true)
|
||||
case reflect.Map, reflect.Struct:
|
||||
rawMap := SerializedObject{}
|
||||
err := yaml.Unmarshal(unsafe.Slice(unsafe.StringData(src), len(src)), &rawMap)
|
||||
if err != nil {
|
||||
return true, gperr.Wrap(err)
|
||||
return true, err
|
||||
}
|
||||
return true, mapUnmarshalValidate(rawMap, dst, true)
|
||||
default:
|
||||
@@ -619,7 +614,7 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gpe
|
||||
|
||||
var envRegex = regexp.MustCompile(`\$\{([^}]+)\}`) // e.g. ${CLOUDFLARE_API_KEY}
|
||||
|
||||
func substituteEnv(data []byte) ([]byte, gperr.Error) {
|
||||
func substituteEnv(data []byte) ([]byte, error) {
|
||||
envError := gperr.NewBuilder("env substitution error")
|
||||
data = envRegex.ReplaceAllFunc(data, func(match []byte) []byte {
|
||||
varName := string(match[2 : len(match)-1])
|
||||
@@ -643,7 +638,7 @@ type (
|
||||
newDecoderFunc func(r io.Reader) interface {
|
||||
Decode(v any) error
|
||||
}
|
||||
interceptFunc func(m map[string]any) gperr.Error
|
||||
interceptFunc func(m map[string]any) error
|
||||
)
|
||||
|
||||
// UnmarshalValidate unmarshals data into a map, applies optional intercept
|
||||
@@ -651,7 +646,7 @@ type (
|
||||
// - Environment variables in the data are substituted using ${VAR} syntax.
|
||||
// - The unmarshaler function converts data to a map[string]any.
|
||||
// - Intercept functions can modify or validate the map before unmarshaling.
|
||||
func UnmarshalValidate[T any](data []byte, target *T, unmarshaler unmarshalFunc, interceptFns ...interceptFunc) gperr.Error {
|
||||
func UnmarshalValidate[T any](data []byte, target *T, unmarshaler unmarshalFunc, interceptFns ...interceptFunc) error {
|
||||
data, err := substituteEnv(data)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -659,7 +654,7 @@ func UnmarshalValidate[T any](data []byte, target *T, unmarshaler unmarshalFunc,
|
||||
|
||||
m := make(map[string]any)
|
||||
if err := unmarshaler(data, &m); err != nil {
|
||||
return gperr.Wrap(err)
|
||||
return err
|
||||
}
|
||||
for _, intercept := range interceptFns {
|
||||
if err := intercept(m); err != nil {
|
||||
@@ -674,10 +669,10 @@ func UnmarshalValidate[T any](data []byte, target *T, unmarshaler unmarshalFunc,
|
||||
// - Environment variables are substituted during reading using ${VAR} syntax.
|
||||
// - The newDecoder function creates a decoder for the reader (e.g.,
|
||||
// json.NewDecoder).
|
||||
func UnmarshalValidateReader[T any](reader io.Reader, target *T, newDecoder newDecoderFunc, interceptFns ...interceptFunc) gperr.Error {
|
||||
func UnmarshalValidateReader[T any](reader io.Reader, target *T, newDecoder newDecoderFunc, interceptFns ...interceptFunc) error {
|
||||
m := make(map[string]any)
|
||||
if err := newDecoder(NewSubstituteEnvReader(reader)).Decode(&m); err != nil {
|
||||
return gperr.Wrap(err)
|
||||
return err
|
||||
}
|
||||
for _, intercept := range interceptFns {
|
||||
if err := intercept(m); err != nil {
|
||||
@@ -692,7 +687,7 @@ func UnmarshalValidateReader[T any](reader io.Reader, target *T, newDecoder newD
|
||||
// - The unmarshaler function converts data to a map[string]any.
|
||||
// - Intercept functions can modify or validate the map before unmarshaling.
|
||||
// - Returns a thread-safe concurrent map with the unmarshaled values.
|
||||
func UnmarshalValidateXSync[V any](data []byte, unmarshaler unmarshalFunc, interceptFns ...interceptFunc) (*xsync.Map[string, V], gperr.Error) {
|
||||
func UnmarshalValidateXSync[V any](data []byte, unmarshaler unmarshalFunc, interceptFns ...interceptFunc) (*xsync.Map[string, V], error) {
|
||||
data, err := substituteEnv(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -700,7 +695,7 @@ func UnmarshalValidateXSync[V any](data []byte, unmarshaler unmarshalFunc, inter
|
||||
|
||||
m := make(map[string]any)
|
||||
if err := unmarshaler(data, &m); err != nil {
|
||||
return nil, gperr.Wrap(err)
|
||||
return nil, err
|
||||
}
|
||||
for _, intercept := range interceptFns {
|
||||
if err := intercept(m); err != nil {
|
||||
|
||||
@@ -42,7 +42,7 @@ func BenchmarkDeserialize(b *testing.B) {
|
||||
dst := complexStruct{}
|
||||
err := MapUnmarshalValidate(src, &dst)
|
||||
if err != nil {
|
||||
b.Fatal(string(err.Plain()))
|
||||
b.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package serialization
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
var validate = validator.New()
|
||||
|
||||
var ErrValidationError = gperr.New("validation error")
|
||||
var ErrValidationError = errors.New("validation error")
|
||||
|
||||
func Validator() *validator.Validate {
|
||||
return validate
|
||||
@@ -23,12 +23,12 @@ func MustRegisterValidation(tag string, fn validator.Func) {
|
||||
}
|
||||
|
||||
type CustomValidator interface {
|
||||
Validate() gperr.Error
|
||||
Validate() error
|
||||
}
|
||||
|
||||
var validatorType = reflect.TypeFor[CustomValidator]()
|
||||
|
||||
func ValidateWithCustomValidator(v reflect.Value) gperr.Error {
|
||||
func ValidateWithCustomValidator(v reflect.Value) error {
|
||||
vt := v.Type()
|
||||
if v.Kind() == reflect.Pointer {
|
||||
elemType := vt.Elem()
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
package serialization
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
// Test cases for when *T implements CustomValidator but T is passed in
|
||||
type CustomValidatingInt int
|
||||
|
||||
func (c *CustomValidatingInt) Validate() gperr.Error {
|
||||
func (c *CustomValidatingInt) Validate() error {
|
||||
if c == nil {
|
||||
return gperr.New("pointer int cannot be nil")
|
||||
return errors.New("pointer int cannot be nil")
|
||||
}
|
||||
if *c <= 0 {
|
||||
return gperr.New("int must be positive")
|
||||
return errors.New("int must be positive")
|
||||
}
|
||||
if *c > 100 {
|
||||
return gperr.New("int must be <= 100")
|
||||
return errors.New("int must be <= 100")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -26,12 +25,12 @@ func (c *CustomValidatingInt) Validate() gperr.Error {
|
||||
// Test cases for when T implements CustomValidator but *T is passed in
|
||||
type CustomValidatingFloat float64
|
||||
|
||||
func (c CustomValidatingFloat) Validate() gperr.Error {
|
||||
func (c CustomValidatingFloat) Validate() error {
|
||||
if c < 0 {
|
||||
return gperr.New("float must be non-negative")
|
||||
return errors.New("float must be non-negative")
|
||||
}
|
||||
if c > 1000 {
|
||||
return gperr.New("float must be <= 1000")
|
||||
return errors.New("float must be <= 1000")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
package serialization
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
type CustomValidatingPointerString string
|
||||
|
||||
func (c *CustomValidatingPointerString) Validate() gperr.Error {
|
||||
func (c *CustomValidatingPointerString) Validate() error {
|
||||
if c == nil {
|
||||
return gperr.New("pointer string cannot be nil")
|
||||
return errors.New("pointer string cannot be nil")
|
||||
}
|
||||
if *c == "" {
|
||||
return gperr.New("string cannot be empty")
|
||||
return errors.New("string cannot be empty")
|
||||
}
|
||||
if len(*c) < 2 {
|
||||
return gperr.New("string must be at least 2 characters")
|
||||
return errors.New("string must be at least 2 characters")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
package serialization
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
type CustomValidatingString string
|
||||
|
||||
func (c CustomValidatingString) Validate() gperr.Error {
|
||||
func (c CustomValidatingString) Validate() error {
|
||||
if c == "" {
|
||||
return gperr.New("string cannot be empty")
|
||||
return errors.New("string cannot be empty")
|
||||
}
|
||||
if len(c) < 2 {
|
||||
return gperr.New("string must be at least 2 characters")
|
||||
return errors.New("string must be at least 2 characters")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
package serialization
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
type CustomValidatingPointerStruct struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
func (c *CustomValidatingPointerStruct) Validate() gperr.Error {
|
||||
func (c *CustomValidatingPointerStruct) Validate() error {
|
||||
if c == nil {
|
||||
return gperr.New("pointer struct cannot be nil")
|
||||
return errors.New("pointer struct cannot be nil")
|
||||
}
|
||||
if c.Value == "" {
|
||||
return gperr.New("value cannot be empty")
|
||||
return errors.New("value cannot be empty")
|
||||
}
|
||||
if len(c.Value) < 3 {
|
||||
return gperr.New("value must be at least 3 characters")
|
||||
return errors.New("value must be at least 3 characters")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
package serialization
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
type CustomValidatingStruct struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
func (c CustomValidatingStruct) Validate() gperr.Error {
|
||||
func (c CustomValidatingStruct) Validate() error {
|
||||
if c.Value == "" {
|
||||
return gperr.New("value cannot be empty")
|
||||
return errors.New("value cannot be empty")
|
||||
}
|
||||
if len(c.Value) < 3 {
|
||||
return gperr.New("value must be at least 3 characters")
|
||||
return errors.New("value must be at least 3 characters")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user