added golangci-linting, refactor, simplified error msgs and fixed some error handling

This commit is contained in:
yusing
2024-10-10 11:52:09 +08:00
parent d91b66ae87
commit da04a0dff4
63 changed files with 690 additions and 410 deletions

View File

@@ -7,7 +7,7 @@ import (
)
// Recursively lists all files in a directory until `maxDepth` is reached
// Returns a slice of file paths relative to `dir`
// Returns a slice of file paths relative to `dir`.
func ListFiles(dir string, maxDepth int) ([]string, error) {
entries, err := os.ReadDir(dir)
if err != nil {

View File

@@ -4,9 +4,8 @@ import (
"sync"
"github.com/puzpuzpuz/xsync/v3"
"gopkg.in/yaml.v3"
E "github.com/yusing/go-proxy/internal/error"
"gopkg.in/yaml.v3"
)
type Map[KT comparable, VT any] struct {
@@ -25,6 +24,17 @@ func NewMapFrom[KT comparable, VT any](m map[KT]VT) (res Map[KT, VT]) {
return
}
// MapFind iterates over the map and returns the first value
// that satisfies the given criteria. The iteration is stopped
// once a value is found. If no value satisfies the criteria,
// the function returns the zero value of CT.
//
// The criteria function takes a value of type VT and returns a
// value of type CT and a boolean indicating whether the value
// satisfies the criteria. The boolean value is used to determine
// whether the iteration should be stopped.
//
// The function is safe for concurrent use.
func MapFind[KT comparable, VT, CT any](m Map[KT, VT], criteria func(VT) (CT, bool)) (_ CT) {
result := make(chan CT, 1)
@@ -49,13 +59,15 @@ func MapFind[KT comparable, VT, CT any](m Map[KT, VT], criteria func(VT) (CT, bo
}
}
// MergeFrom add contents from another `Map`, ignore duplicated keys
// MergeFrom merges the contents of another Map into this one, ignoring duplicated keys.
//
// Parameters:
// - other: `Map` of values to add from
//
// Return:
// - Map: a `Map` of duplicated keys-value pairs
// other: Map of values to add from
//
// Returns:
//
// Map of duplicated keys-value pairs
func (m Map[KT, VT]) MergeFrom(other Map[KT, VT]) Map[KT, VT] {
dups := NewMapOf[KT, VT]()
@@ -70,6 +82,15 @@ func (m Map[KT, VT]) MergeFrom(other Map[KT, VT]) Map[KT, VT] {
return dups
}
// RangeAll calls the given function for each key-value pair in the map.
//
// Parameters:
//
// do: function to call for each key-value pair
//
// Returns:
//
// nothing
func (m Map[KT, VT]) RangeAll(do func(k KT, v VT)) {
m.Range(func(k KT, v VT) bool {
do(k, v)
@@ -77,6 +98,16 @@ func (m Map[KT, VT]) RangeAll(do func(k KT, v VT)) {
})
}
// RangeAllParallel calls the given function for each key-value pair in the map,
// in parallel. The map is not safe for modification from within the function.
//
// Parameters:
//
// do: function to call for each key-value pair
//
// Returns:
//
// nothing
func (m Map[KT, VT]) RangeAllParallel(do func(k KT, v VT)) {
var wg sync.WaitGroup
wg.Add(m.Size())
@@ -91,6 +122,15 @@ func (m Map[KT, VT]) RangeAllParallel(do func(k KT, v VT)) {
wg.Wait()
}
// RemoveAll removes all key-value pairs from the map where the value matches the given criteria.
//
// Parameters:
//
// criteria: function to determine whether a value should be removed
//
// Returns:
//
// nothing
func (m Map[KT, VT]) RemoveAll(criteria func(VT) bool) {
m.Range(func(k KT, v VT) bool {
if criteria(v) {
@@ -105,6 +145,17 @@ func (m Map[KT, VT]) Has(k KT) bool {
return ok
}
// UnmarshalFromYAML unmarshals a yaml byte slice into the map.
//
// It overwrites all existing key-value pairs in the map.
//
// Parameters:
//
// data: yaml byte slice to unmarshal
//
// Returns:
//
// error: if the unmarshaling fails
func (m Map[KT, VT]) UnmarshalFromYAML(data []byte) E.NestedError {
if m.Size() != 0 {
return E.FailedWhy("unmarshal from yaml", "map is not empty")

View File

@@ -12,7 +12,7 @@ import (
E "github.com/yusing/go-proxy/internal/error"
)
// TODO: move to "utils/io"
// TODO: move to "utils/io".
type (
FileReader struct {
Path string
@@ -108,7 +108,7 @@ func Copy2(ctx context.Context, dst io.Writer, src io.Reader) error {
return Copy(&ContextWriter{ctx: ctx, Writer: dst}, &ContextReader{ctx: ctx, Reader: src})
}
func LoadJson[T any](path string, pointer *T) E.NestedError {
func LoadJSON[T any](path string, pointer *T) E.NestedError {
data, err := E.Check(os.ReadFile(path))
if err.HasError() {
return err
@@ -116,7 +116,7 @@ func LoadJson[T any](path string, pointer *T) E.NestedError {
return E.From(json.Unmarshal(data, pointer))
}
func SaveJson[T any](path string, pointer *T, perm os.FileMode) E.NestedError {
func SaveJSON[T any](path string, pointer *T, perm os.FileMode) E.NestedError {
data, err := E.Check(json.Marshal(pointer))
if err.HasError() {
return err

View File

@@ -3,6 +3,7 @@ package utils
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"reflect"
"strconv"
@@ -14,10 +15,12 @@ import (
"gopkg.in/yaml.v3"
)
type SerializedObject = map[string]any
type Converter interface {
ConvertFrom(value any) (any, E.NestedError)
}
type (
SerializedObject = map[string]any
Converter interface {
ConvertFrom(value any) (any, E.NestedError)
}
)
func ValidateYaml(schema *jsonschema.Schema, data []byte) E.NestedError {
var i any
@@ -37,11 +40,16 @@ func ValidateYaml(schema *jsonschema.Schema, data []byte) E.NestedError {
return nil
}
errors := E.NewBuilder("yaml validation error")
for _, e := range err.(*jsonschema.ValidationError).Causes {
errors.AddE(e)
var valErr *jsonschema.ValidationError
if !errors.As(err, &valErr) {
return E.UnexpectedError(err)
}
return errors.Build()
b := E.NewBuilder("yaml validation error")
for _, e := range valErr.Causes {
b.AddE(e)
}
return b.Build()
}
// Serialize converts the given data into a map[string]any representation.
@@ -80,7 +88,7 @@ func Serialize(data any) (SerializedObject, E.NestedError) {
result[key.String()] = value.MapIndex(key).Interface()
}
case reflect.Struct:
for i := 0; i < value.NumField(); i++ {
for i := range value.NumField() {
field := value.Type().Field(i)
if !field.IsExported() {
continue
@@ -91,9 +99,10 @@ func Serialize(data any) (SerializedObject, E.NestedError) {
}
// If the json tag is not empty, use it as the key
if jsonTag != "" {
switch {
case jsonTag != "":
result[jsonTag] = value.Field(i).Interface()
} else if field.Anonymous {
case field.Anonymous:
// If the field is an embedded struct, add its fields to the result
fieldMap, err := Serialize(value.Field(i).Interface())
if err != nil {
@@ -102,7 +111,7 @@ func Serialize(data any) (SerializedObject, E.NestedError) {
for k, v := range fieldMap {
result[k] = v
}
} else {
default:
result[field.Name] = value.Field(i).Interface()
}
}
@@ -147,7 +156,8 @@ func Deserialize(src SerializedObject, dst any) E.NestedError {
// TODO: use E.Builder to collect errors from all fields
if dstV.Kind() == reflect.Struct {
switch dstV.Kind() {
case reflect.Struct:
mapping := make(map[string]reflect.Value)
for _, field := range reflect.VisibleFields(dstT) {
mapping[ToLowerNoSnake(field.Name)] = dstV.FieldByName(field.Name)
@@ -162,7 +172,7 @@ func Deserialize(src SerializedObject, dst any) E.NestedError {
return E.Unexpected("field", k).Subjectf("%T", dst)
}
}
} else if dstV.Kind() == reflect.Map && dstT.Key().Kind() == reflect.String {
case reflect.Map:
if dstV.IsNil() {
dstV.Set(reflect.MakeMap(dstT))
}
@@ -174,8 +184,7 @@ func Deserialize(src SerializedObject, dst any) E.NestedError {
}
dstV.SetMapIndex(reflect.ValueOf(ToLowerNoSnake(k)), tmp)
}
return nil
} else {
default:
return E.Unsupported("target type", fmt.Sprintf("%T", dst))
}
@@ -362,7 +371,7 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr E.N
return true, Convert(reflect.ValueOf(tmp), dst)
}
func DeserializeJson(j map[string]string, target any) E.NestedError {
func DeserializeJSON(j map[string]string, target any) E.NestedError {
data, err := E.Check(json.Marshal(j))
if err != nil {
return err

View File

@@ -9,7 +9,7 @@ import (
"golang.org/x/text/language"
)
// TODO: support other languages
// TODO: support other languages.
var titleCaser = cases.Title(language.AmericanEnglish)
func CommaSeperatedList(s string) []string {
@@ -31,3 +31,7 @@ func ExtractPort(fullURL string) (int, error) {
}
return strconv.Atoi(url.Port())
}
func PortString(port uint16) string {
return strconv.FormatUint(uint64(port), 10)
}

View File

@@ -92,7 +92,6 @@ func ExpectType[T any](t *testing.T, got any) (_ T) {
_, ok := got.(T)
if !ok {
t.Fatalf("expected type %s, got %s", tExpect, reflect.TypeOf(got).Elem())
t.FailNow()
return
}
return got.(T)