mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-20 16:23:53 +01:00
v0.5.0-rc5: check release
This commit is contained in:
@@ -1,229 +1,116 @@
|
||||
package functional
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
E "github.com/yusing/go-proxy/error"
|
||||
)
|
||||
|
||||
type Map[KT comparable, VT any] struct {
|
||||
m map[KT]VT
|
||||
defVals map[KT]VT
|
||||
sync.RWMutex
|
||||
*xsync.MapOf[KT, VT]
|
||||
}
|
||||
|
||||
// NewMap creates a new Map with the given map as its initial values.
|
||||
//
|
||||
// Parameters:
|
||||
// - dv: optional default values for the Map
|
||||
//
|
||||
// Return:
|
||||
// - *Map[KT, VT]: a pointer to the newly created Map.
|
||||
func NewMap[KT comparable, VT any](dv ...map[KT]VT) *Map[KT, VT] {
|
||||
return NewMapFrom(make(map[KT]VT), dv...)
|
||||
func NewMapOf[KT comparable, VT any](options ...func(*xsync.MapConfig)) Map[KT, VT] {
|
||||
return Map[KT, VT]{xsync.NewMapOf[KT, VT](options...)}
|
||||
}
|
||||
|
||||
// NewMapOf creates a new Map with the given map as its initial values.
|
||||
//
|
||||
// Type parameters:
|
||||
// - M: type for the new map.
|
||||
//
|
||||
// Parameters:
|
||||
// - dv: optional default values for the Map
|
||||
//
|
||||
// Return:
|
||||
// - *Map[KT, VT]: a pointer to the newly created Map.
|
||||
func NewMapOf[M Map[KT, VT], KT comparable, VT any](dv ...map[KT]VT) *Map[KT, VT] {
|
||||
return NewMapFrom(make(map[KT]VT), dv...)
|
||||
}
|
||||
|
||||
// NewMapFrom creates a new Map with the given map as its initial values.
|
||||
//
|
||||
// Parameters:
|
||||
// - from: a map of type KT to VT, which will be the initial values of the Map.
|
||||
// - dv: optional default values for the Map
|
||||
//
|
||||
// Return:
|
||||
// - *Map[KT, VT]: a pointer to the newly created Map.
|
||||
func NewMapFrom[KT comparable, VT any](from map[KT]VT, dv ...map[KT]VT) *Map[KT, VT] {
|
||||
if len(dv) > 0 {
|
||||
return &Map[KT, VT]{m: from, defVals: dv[0]}
|
||||
func NewMapFrom[KT comparable, VT any](m map[KT]VT) (res Map[KT, VT]) {
|
||||
res = NewMapOf[KT, VT](xsync.WithPresize(len(m)))
|
||||
for k, v := range m {
|
||||
res.Store(k, v)
|
||||
}
|
||||
return &Map[KT, VT]{m: from}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Map[KT, VT]) Set(key KT, value VT) {
|
||||
m.Lock()
|
||||
m.m[key] = value
|
||||
m.Unlock()
|
||||
}
|
||||
func MapFind[KT comparable, VT, CT any](m Map[KT, VT], criteria func(VT) (CT, bool)) (_ CT) {
|
||||
result := make(chan CT, 1)
|
||||
|
||||
func (m *Map[KT, VT]) Get(key KT) VT {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
value, ok := m.m[key]
|
||||
if !ok && m.defVals != nil {
|
||||
return m.defVals[key]
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Find searches for the first element in the map that satisfies the given criteria.
|
||||
//
|
||||
// Parameters:
|
||||
// - criteria: a function that takes a value of type VT and returns a tuple of any type and a boolean.
|
||||
//
|
||||
// Return:
|
||||
// - any: the first value that satisfies the criteria, or nil if no match is found.
|
||||
func (m *Map[KT, VT]) Find(criteria func(VT) (any, bool)) any {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
result := make(chan any)
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
for _, v := range m.m {
|
||||
wg.Add(1)
|
||||
go func(val VT) {
|
||||
defer wg.Done()
|
||||
if value, ok := criteria(val); ok {
|
||||
select {
|
||||
case result <- value:
|
||||
cancel() // Cancel other goroutines if a result is found
|
||||
case <-ctx.Done(): // If already cancelled
|
||||
return
|
||||
}
|
||||
m.Range(func(key KT, value VT) bool {
|
||||
select {
|
||||
case <-result: // already have a result
|
||||
return false // stop iteration
|
||||
default:
|
||||
if got, ok := criteria(value); ok {
|
||||
result <- got
|
||||
return false
|
||||
}
|
||||
}(v)
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(result)
|
||||
}()
|
||||
|
||||
// The first valid match, if any
|
||||
select {
|
||||
case res, ok := <-result:
|
||||
if ok {
|
||||
return res
|
||||
return true
|
||||
}
|
||||
case <-ctx.Done():
|
||||
})
|
||||
|
||||
select {
|
||||
case v := <-result:
|
||||
return v
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
return nil // Return nil if no matches found
|
||||
}
|
||||
|
||||
func (m *Map[KT, VT]) UnsafeGet(key KT) (VT, bool) {
|
||||
value, ok := m.m[key]
|
||||
return value, ok
|
||||
}
|
||||
|
||||
func (m *Map[KT, VT]) UnsafeSet(key KT, value VT) {
|
||||
m.m[key] = value
|
||||
}
|
||||
|
||||
func (m *Map[KT, VT]) Delete(key KT) {
|
||||
m.Lock()
|
||||
delete(m.m, key)
|
||||
m.Unlock()
|
||||
}
|
||||
|
||||
func (m *Map[KT, VT]) UnsafeDelete(key KT) {
|
||||
delete(m.m, key)
|
||||
}
|
||||
|
||||
// MergeWith merges the contents of another Map[KT, VT]
|
||||
// into the current Map[KT, VT] and
|
||||
// returns a map that were duplicated.
|
||||
// MergeFrom add contents from another `Map`, ignore duplicated keys
|
||||
//
|
||||
// Parameters:
|
||||
// - other: a pointer to another Map[KT, VT] to be merged into the current Map[KT, VT].
|
||||
// - other: `Map` of values to add from
|
||||
//
|
||||
// Return:
|
||||
// - Map[KT, VT]: a map of key-value pairs that were duplicated during the merge.
|
||||
func (m *Map[KT, VT]) MergeWith(other *Map[KT, VT]) Map[KT, VT] {
|
||||
dups := make(map[KT]VT)
|
||||
// - Map: a `Map` of duplicated keys-value pairs
|
||||
func (m Map[KT, VT]) MergeFrom(other Map[KT, VT]) Map[KT, VT] {
|
||||
dups := NewMapOf[KT, VT]()
|
||||
|
||||
m.Lock()
|
||||
for k, v := range other.m {
|
||||
if _, isDup := m.m[k]; !isDup {
|
||||
m.m[k] = v
|
||||
other.Range(func(k KT, v VT) bool {
|
||||
if _, ok := m.Load(k); ok {
|
||||
dups.Store(k, v)
|
||||
} else {
|
||||
dups[k] = v
|
||||
m.Store(k, v)
|
||||
}
|
||||
}
|
||||
m.Unlock()
|
||||
return Map[KT, VT]{m: dups}
|
||||
return true
|
||||
})
|
||||
return dups
|
||||
}
|
||||
|
||||
func (m *Map[KT, VT]) Clear() {
|
||||
m.Lock()
|
||||
m.m = make(map[KT]VT)
|
||||
m.Unlock()
|
||||
func (m Map[KT, VT]) RangeAll(do func(k KT, v VT)) {
|
||||
m.Range(func(k KT, v VT) bool {
|
||||
do(k, v)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (m *Map[KT, VT]) Size() int {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
return len(m.m)
|
||||
func (m Map[KT, VT]) RemoveAll(criteria func(VT) bool) {
|
||||
m.Range(func(k KT, v VT) bool {
|
||||
if criteria(v) {
|
||||
m.Delete(k)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (m *Map[KT, VT]) Contains(key KT) bool {
|
||||
m.RLock()
|
||||
_, ok := m.m[key]
|
||||
m.RUnlock()
|
||||
func (m Map[KT, VT]) Has(k KT) bool {
|
||||
_, ok := m.Load(k)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (m *Map[KT, VT]) Clone() *Map[KT, VT] {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
clone := make(map[KT]VT, len(m.m))
|
||||
for k, v := range m.m {
|
||||
clone[k] = v
|
||||
func (m Map[KT, VT]) UnmarshalFromYAML(data []byte) E.NestedError {
|
||||
if m.Size() != 0 {
|
||||
return E.FailedWhy("unmarshal from yaml", "map is not empty")
|
||||
}
|
||||
return &Map[KT, VT]{m: clone, defVals: m.defVals}
|
||||
}
|
||||
|
||||
func (m *Map[KT, VT]) EachKV(fn func(k KT, v VT)) {
|
||||
m.Lock()
|
||||
for k, v := range m.m {
|
||||
fn(k, v)
|
||||
tmp := make(map[KT]VT)
|
||||
if err := E.From(yaml.Unmarshal(data, tmp)); err.HasError() {
|
||||
return err
|
||||
}
|
||||
m.Unlock()
|
||||
}
|
||||
|
||||
func (m *Map[KT, VT]) Each(fn func(v VT)) {
|
||||
m.Lock()
|
||||
for _, v := range m.m {
|
||||
fn(v)
|
||||
for k, v := range tmp {
|
||||
m.Store(k, v)
|
||||
}
|
||||
m.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Map[KT, VT]) EachParallel(fn func(v VT)) {
|
||||
m.Lock()
|
||||
ParallelForEachValue(m.m, fn)
|
||||
m.Unlock()
|
||||
}
|
||||
|
||||
func (m *Map[KT, VT]) EachKVParallel(fn func(k KT, v VT)) {
|
||||
m.Lock()
|
||||
ParallelForEachKV(m.m, fn)
|
||||
m.Unlock()
|
||||
}
|
||||
|
||||
func (m *Map[KT, VT]) UnmarshalFromYAML(data []byte) E.NestedError {
|
||||
return E.From(yaml.Unmarshal(data, m.m))
|
||||
}
|
||||
|
||||
func (m *Map[KT, VT]) Iterator() map[KT]VT {
|
||||
return m.m
|
||||
func (m Map[KT, VT]) String() string {
|
||||
tmp := make(map[KT]VT, m.Size())
|
||||
m.RangeAll(func(k KT, v VT) {
|
||||
tmp[k] = v
|
||||
})
|
||||
data, err := yaml.Marshal(tmp)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
75
src/utils/functional/map_test.go
Normal file
75
src/utils/functional/map_test.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package functional_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/yusing/go-proxy/utils/functional"
|
||||
. "github.com/yusing/go-proxy/utils/testing"
|
||||
)
|
||||
|
||||
func TestNewMapFrom(t *testing.T) {
|
||||
m := NewMapFrom(map[string]int{
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"c": 3,
|
||||
})
|
||||
ExpectEqual(t, m.Size(), 3)
|
||||
ExpectTrue(t, m.Has("a"))
|
||||
ExpectTrue(t, m.Has("b"))
|
||||
ExpectTrue(t, m.Has("c"))
|
||||
}
|
||||
|
||||
func TestMapFind(t *testing.T) {
|
||||
m := NewMapFrom(map[string]map[string]int{
|
||||
"a": {
|
||||
"a": 1,
|
||||
},
|
||||
"b": {
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
},
|
||||
"c": {
|
||||
"b": 2,
|
||||
"c": 3,
|
||||
},
|
||||
})
|
||||
res := MapFind(m, func(inner map[string]int) (int, bool) {
|
||||
if _, ok := inner["c"]; ok && inner["c"] == 3 {
|
||||
return inner["c"], true
|
||||
}
|
||||
return 0, false
|
||||
})
|
||||
ExpectEqual(t, res, 3)
|
||||
}
|
||||
|
||||
func TestMergeFrom(t *testing.T) {
|
||||
m1 := NewMapFrom(map[string]int{
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"c": 3,
|
||||
"d": 4,
|
||||
})
|
||||
m2 := NewMapFrom(map[string]int{
|
||||
"a": 1,
|
||||
"c": 123,
|
||||
"e": 456,
|
||||
"f": 6,
|
||||
})
|
||||
dup := m1.MergeFrom(m2)
|
||||
|
||||
ExpectEqual(t, m1.Size(), 6)
|
||||
ExpectTrue(t, m1.Has("e"))
|
||||
ExpectTrue(t, m1.Has("f"))
|
||||
c, _ := m1.Load("c")
|
||||
d, _ := m1.Load("d")
|
||||
e, _ := m1.Load("e")
|
||||
f, _ := m1.Load("f")
|
||||
ExpectEqual(t, c, 3)
|
||||
ExpectEqual(t, d, 4)
|
||||
ExpectEqual(t, e, 456)
|
||||
ExpectEqual(t, f, 6)
|
||||
|
||||
ExpectEqual(t, dup.Size(), 2)
|
||||
ExpectTrue(t, dup.Has("a"))
|
||||
ExpectTrue(t, dup.Has("c"))
|
||||
}
|
||||
104
src/utils/io.go
104
src/utils/io.go
@@ -10,15 +10,8 @@ import (
|
||||
E "github.com/yusing/go-proxy/error"
|
||||
)
|
||||
|
||||
// TODO: move to "utils/io"
|
||||
type (
|
||||
Reader interface {
|
||||
Read() ([]byte, E.NestedError)
|
||||
}
|
||||
|
||||
StdReader struct {
|
||||
r Reader
|
||||
}
|
||||
|
||||
FileReader struct {
|
||||
Path string
|
||||
}
|
||||
@@ -29,13 +22,6 @@ type (
|
||||
closed atomic.Bool
|
||||
}
|
||||
|
||||
StdReadCloser struct {
|
||||
r *ReadCloser
|
||||
}
|
||||
|
||||
ByteReader []byte
|
||||
NewByteReader = ByteReader
|
||||
|
||||
Pipe struct {
|
||||
r ReadCloser
|
||||
w io.WriteCloser
|
||||
@@ -44,49 +30,25 @@ type (
|
||||
}
|
||||
|
||||
BidirectionalPipe struct {
|
||||
pSrcDst Pipe
|
||||
pDstSrc Pipe
|
||||
pSrcDst *Pipe
|
||||
pDstSrc *Pipe
|
||||
}
|
||||
)
|
||||
|
||||
func NewFileReader(path string) *FileReader {
|
||||
return &FileReader{Path: path}
|
||||
}
|
||||
|
||||
func (r StdReader) Read() ([]byte, error) {
|
||||
return r.r.Read()
|
||||
}
|
||||
|
||||
func (r *FileReader) Read() ([]byte, E.NestedError) {
|
||||
return E.Check(os.ReadFile(r.Path))
|
||||
}
|
||||
|
||||
func (r ByteReader) Read() ([]byte, E.NestedError) {
|
||||
return r, E.Nil()
|
||||
}
|
||||
|
||||
func (r *ReadCloser) Read(p []byte) (int, E.NestedError) {
|
||||
func (r *ReadCloser) Read(p []byte) (int, error) {
|
||||
select {
|
||||
case <-r.ctx.Done():
|
||||
return 0, E.From(r.ctx.Err())
|
||||
return 0, r.ctx.Err()
|
||||
default:
|
||||
return E.Check(r.r.Read(p))
|
||||
return r.r.Read(p)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ReadCloser) Close() E.NestedError {
|
||||
func (r *ReadCloser) Close() error {
|
||||
if r.closed.Load() {
|
||||
return E.Nil()
|
||||
return nil
|
||||
}
|
||||
r.closed.Store(true)
|
||||
return E.From(r.r.Close())
|
||||
}
|
||||
|
||||
func (r StdReadCloser) Read(p []byte) (int, error) {
|
||||
return r.r.Read(p)
|
||||
}
|
||||
|
||||
func (r StdReadCloser) Close() error {
|
||||
return r.r.Close()
|
||||
}
|
||||
|
||||
@@ -100,35 +62,35 @@ func NewPipe(ctx context.Context, r io.ReadCloser, w io.WriteCloser) *Pipe {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pipe) Start() E.NestedError {
|
||||
return Copy(p.ctx, p.w, &StdReadCloser{&p.r})
|
||||
func (p *Pipe) Start() error {
|
||||
return Copy(p.ctx, p.w, &p.r)
|
||||
}
|
||||
|
||||
func (p *Pipe) Stop() E.NestedError {
|
||||
func (p *Pipe) Stop() error {
|
||||
p.cancel()
|
||||
return E.Join("error stopping pipe", p.r.Close(), p.w.Close())
|
||||
return E.JoinE("error stopping pipe", p.r.Close(), p.w.Close()).Error()
|
||||
}
|
||||
|
||||
func (p *Pipe) Write(b []byte) (int, E.NestedError) {
|
||||
return E.Check(p.w.Write(b))
|
||||
func (p *Pipe) Write(b []byte) (int, error) {
|
||||
return p.w.Write(b)
|
||||
}
|
||||
|
||||
func NewBidirectionalPipe(ctx context.Context, rw1 io.ReadWriteCloser, rw2 io.ReadWriteCloser) *BidirectionalPipe {
|
||||
return &BidirectionalPipe{
|
||||
pSrcDst: *NewPipe(ctx, rw1, rw2),
|
||||
pDstSrc: *NewPipe(ctx, rw2, rw1),
|
||||
pSrcDst: NewPipe(ctx, rw1, rw2),
|
||||
pDstSrc: NewPipe(ctx, rw2, rw1),
|
||||
}
|
||||
}
|
||||
|
||||
func NewBidirectionalPipeIntermediate(ctx context.Context, listener io.ReadCloser, client io.ReadWriteCloser, target io.ReadWriteCloser) *BidirectionalPipe {
|
||||
return &BidirectionalPipe{
|
||||
pSrcDst: *NewPipe(ctx, listener, client),
|
||||
pDstSrc: *NewPipe(ctx, client, target),
|
||||
pSrcDst: NewPipe(ctx, listener, client),
|
||||
pDstSrc: NewPipe(ctx, client, target),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *BidirectionalPipe) Start() E.NestedError {
|
||||
errCh := make(chan E.NestedError, 2)
|
||||
func (p *BidirectionalPipe) Start() error {
|
||||
errCh := make(chan error, 2)
|
||||
go func() {
|
||||
errCh <- p.pSrcDst.Start()
|
||||
}()
|
||||
@@ -136,34 +98,34 @@ func (p *BidirectionalPipe) Start() E.NestedError {
|
||||
errCh <- p.pDstSrc.Start()
|
||||
}()
|
||||
for err := range errCh {
|
||||
if err.HasError() {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return E.Nil()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *BidirectionalPipe) Stop() E.NestedError {
|
||||
return E.Join("error stopping pipe", p.pSrcDst.Stop(), p.pDstSrc.Stop())
|
||||
func (p *BidirectionalPipe) Stop() error {
|
||||
return E.JoinE("error stopping pipe", p.pSrcDst.Stop(), p.pDstSrc.Stop()).Error()
|
||||
}
|
||||
|
||||
func Copy(ctx context.Context, dst io.WriteCloser, src io.ReadCloser) E.NestedError {
|
||||
_, err := io.Copy(dst, StdReadCloser{&ReadCloser{ctx: ctx, r: src}})
|
||||
return E.From(err)
|
||||
func Copy(ctx context.Context, dst io.WriteCloser, src io.ReadCloser) error {
|
||||
_, err := io.Copy(dst, &ReadCloser{ctx: ctx, r: src})
|
||||
return err
|
||||
}
|
||||
|
||||
func LoadJson[T any](path string, pointer *T) E.NestedError {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return E.From(err)
|
||||
data, err := E.Check(os.ReadFile(path))
|
||||
if err.HasError() {
|
||||
return err
|
||||
}
|
||||
return E.From(json.Unmarshal(data, pointer))
|
||||
}
|
||||
|
||||
func SaveJson[T any](path string, pointer *T, perm os.FileMode) E.NestedError {
|
||||
data, err := json.Marshal(pointer)
|
||||
if err != nil {
|
||||
return E.From(err)
|
||||
data, err := E.Check(json.Marshal(pointer))
|
||||
if err.HasError() {
|
||||
return err
|
||||
}
|
||||
return E.From(os.WriteFile(path, data, perm))
|
||||
}
|
||||
|
||||
@@ -20,5 +20,5 @@ func SetFieldFromSnake[T, VT any](obj *T, field string, value VT) E.NestedError
|
||||
return E.Invalid("field", field)
|
||||
}
|
||||
prop.Set(reflect.ValueOf(value))
|
||||
return E.Nil()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -17,45 +17,26 @@ func ValidateYaml(schema *jsonschema.Schema, data []byte) E.NestedError {
|
||||
|
||||
err := yaml.Unmarshal(data, &i)
|
||||
if err != nil {
|
||||
return E.Failure("unmarshal yaml").With(err)
|
||||
return E.FailWith("unmarshal yaml", err)
|
||||
}
|
||||
|
||||
m, err := json.Marshal(i)
|
||||
if err != nil {
|
||||
return E.Failure("marshal json").With(err)
|
||||
return E.FailWith("marshal json", err)
|
||||
}
|
||||
|
||||
err = schema.Validate(bytes.NewReader(m))
|
||||
if err == nil {
|
||||
return E.Nil()
|
||||
return nil
|
||||
}
|
||||
|
||||
errors := E.NewBuilder("yaml validation error")
|
||||
for _, e := range err.(*jsonschema.ValidationError).Causes {
|
||||
errors.Add(e)
|
||||
errors.AddE(e)
|
||||
}
|
||||
return errors.Build()
|
||||
}
|
||||
|
||||
// TryJsonStringify converts the given object to a JSON string.
|
||||
//
|
||||
// It takes an object of any type and attempts to marshal it into a JSON string.
|
||||
// If the marshaling is successful, the JSON string is returned.
|
||||
// If the marshaling fails, the object is converted to a string using fmt.Sprint and returned.
|
||||
//
|
||||
// Parameters:
|
||||
// - o: The object to be converted to a JSON string.
|
||||
//
|
||||
// Return type:
|
||||
// - string: The JSON string representation of the object.
|
||||
func TryJsonStringify(o any) string {
|
||||
b, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
return fmt.Sprint(o)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// Serialize converts the given data into a map[string]any representation.
|
||||
//
|
||||
// It uses reflection to inspect the data type and handle different kinds of data.
|
||||
@@ -123,7 +104,7 @@ func Serialize(data any) (SerializedObject, E.NestedError) {
|
||||
return nil, E.Unsupported("type", value.Kind())
|
||||
}
|
||||
|
||||
return result, E.Nil()
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func Deserialize(src map[string]any, target any) E.NestedError {
|
||||
@@ -166,7 +147,7 @@ func Deserialize(src map[string]any, target any) E.NestedError {
|
||||
propNew := reflect.New(propType.Elem())
|
||||
err := Deserialize(vSerialized, propNew.Interface())
|
||||
if err.HasError() {
|
||||
return E.Failure("set field").With(k).With(err)
|
||||
return E.Failure("set field").With(err).Subject(k)
|
||||
}
|
||||
prop.Set(propNew)
|
||||
default:
|
||||
@@ -180,7 +161,15 @@ func Deserialize(src map[string]any, target any) E.NestedError {
|
||||
}
|
||||
}
|
||||
|
||||
return E.Nil()
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeserializeJson(j map[string]string, target any) E.NestedError {
|
||||
data, err := E.Check(json.Marshal(j))
|
||||
if err.HasError() {
|
||||
return err
|
||||
}
|
||||
return E.From(json.Unmarshal(data, target))
|
||||
}
|
||||
|
||||
func toLowerNoSnake(s string) string {
|
||||
|
||||
11
src/utils/string.go
Normal file
11
src/utils/string.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package utils
|
||||
|
||||
import "strings"
|
||||
|
||||
func CommaSeperatedList(s string) []string {
|
||||
res := strings.Split(s, ",")
|
||||
for i, part := range res {
|
||||
res[i] = strings.TrimSpace(part)
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -3,25 +3,23 @@ package utils
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
E "github.com/yusing/go-proxy/error"
|
||||
)
|
||||
|
||||
func ExpectNoError(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
var noError bool
|
||||
switch t := err.(type) {
|
||||
case E.NestedError:
|
||||
noError = t.NoError()
|
||||
default:
|
||||
noError = err == nil
|
||||
}
|
||||
if !noError {
|
||||
if err != nil && !reflect.ValueOf(err).IsNil() {
|
||||
t.Errorf("expected err=nil, got %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func ExpectEqual(t *testing.T, got, want any) {
|
||||
func ExpectEqual[T comparable](t *testing.T, got T, want T) {
|
||||
t.Helper()
|
||||
if got != want {
|
||||
t.Errorf("expected:\n%v, got\n%v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func ExpectDeepEqual[T any](t *testing.T, got T, want T) {
|
||||
t.Helper()
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("expected:\n%v, got\n%v", want, got)
|
||||
@@ -47,7 +45,7 @@ func ExpectType[T any](t *testing.T, got any) T {
|
||||
tExpect := reflect.TypeFor[T]()
|
||||
_, ok := got.(T)
|
||||
if !ok {
|
||||
t.Errorf("expected type %T, got %T", tExpect, got)
|
||||
t.Errorf("expected type %s, got %T", tExpect, got)
|
||||
}
|
||||
return got.(T)
|
||||
}
|
||||
Reference in New Issue
Block a user