replace all schema check with go-playground/validator/v10

This commit is contained in:
yusing 2024-12-18 04:48:29 +08:00
parent 00f60a6e78
commit 6aefe4d5d9
23 changed files with 149 additions and 250 deletions

View file

@ -44,9 +44,6 @@ COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
# copy binary
COPY --from=builder /app /app
# copy schema directory
COPY schema/ /app/schema/
# copy example config
COPY config.example.yml /app/config/config.yml

1
go.mod
View file

@ -14,7 +14,6 @@ require (
github.com/prometheus/client_golang v1.20.5
github.com/puzpuzpuz/xsync/v3 v3.4.0
github.com/rs/zerolog v1.33.0
github.com/santhosh-tekuri/jsonschema v1.2.4
golang.org/x/net v0.32.0
golang.org/x/text v0.21.0
golang.org/x/time v0.8.0

2
go.sum
View file

@ -128,8 +128,6 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis=
github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

View file

@ -27,6 +27,9 @@ var (
)
func NewConfig(cfg *types.AutoCertConfig) *Config {
if cfg == nil {
cfg = new(types.AutoCertConfig)
}
if cfg.CertPath == "" {
cfg.CertPath = CertFileDefault
}

View file

@ -14,12 +14,11 @@ import (
var (
prefixes = []string{"GODOXY_", "GOPROXY_", ""}
NoSchemaValidation = GetEnvBool("NO_SCHEMA_VALIDATION", true)
IsTest = GetEnvBool("TEST", false) || strings.HasSuffix(os.Args[0], ".test")
IsDebug = GetEnvBool("DEBUG", IsTest)
IsDebugSkipAuth = GetEnvBool("DEBUG_SKIP_AUTH", false)
IsTrace = GetEnvBool("TRACE", false) && IsDebug
IsProduction = !IsTest && !IsDebug
IsTest = GetEnvBool("TEST", false) || strings.HasSuffix(os.Args[0], ".test")
IsDebug = GetEnvBool("DEBUG", IsTest)
IsDebugSkipAuth = GetEnvBool("DEBUG_SKIP_AUTH", false)
IsTrace = GetEnvBool("TRACE", false) && IsDebug
IsProduction = !IsTest && !IsDebug
ProxyHTTPAddr,
ProxyHTTPHost,

View file

@ -16,11 +16,10 @@ import (
"github.com/yusing/go-proxy/internal/notif"
proxy "github.com/yusing/go-proxy/internal/route/provider"
"github.com/yusing/go-proxy/internal/task"
U "github.com/yusing/go-proxy/internal/utils"
"github.com/yusing/go-proxy/internal/utils"
F "github.com/yusing/go-proxy/internal/utils/functional"
"github.com/yusing/go-proxy/internal/watcher"
"github.com/yusing/go-proxy/internal/watcher/events"
"gopkg.in/yaml.v3"
)
type Config struct {
@ -68,7 +67,8 @@ func Load() (*Config, E.Error) {
}
func Validate(data []byte) E.Error {
return U.ValidateYaml(U.GetSchema(common.ConfigSchemaPath), data)
var model *types.Config
return utils.DeserializeYAML(data, model)
}
func MatchDomains() []string {
@ -160,14 +160,8 @@ func (cfg *Config) load() E.Error {
E.LogFatal(errMsg, err, &logger)
}
if !common.NoSchemaValidation {
if err := Validate(data); err != nil {
E.LogFatal(errMsg, err, &logger)
}
}
model := types.DefaultConfig()
if err := E.From(yaml.Unmarshal(data, model)); err != nil {
if err := utils.DeserializeYAML(data, model); err != nil {
E.LogFatal(errMsg, err, &logger)
}
@ -176,7 +170,7 @@ func (cfg *Config) load() E.Error {
errs.Add(entrypoint.SetMiddlewares(model.Entrypoint.Middlewares))
errs.Add(entrypoint.SetAccessLogger(cfg.task, model.Entrypoint.AccessLog))
errs.Add(cfg.initNotification(model.Providers.Notification))
errs.Add(cfg.initAutoCert(&model.AutoCert))
errs.Add(cfg.initAutoCert(model.AutoCert))
errs.Add(cfg.loadRouteProviders(&model.Providers))
cfg.value = model

View file

@ -2,13 +2,13 @@ package types
type (
AutoCertConfig struct {
Email string `json:"email,omitempty" yaml:"email"`
Domains []string `json:"domains,omitempty" yaml:",flow"`
CertPath string `json:"cert_path,omitempty" yaml:"cert_path"`
KeyPath string `json:"key_path,omitempty" yaml:"key_path"`
ACMEKeyPath string `json:"acme_key_path,omitempty" yaml:"acme_key_path"`
Provider string `json:"provider,omitempty" yaml:"provider"`
Options AutocertProviderOpt `json:"options,omitempty" yaml:",flow"`
Email string `json:"email,omitempty" validate:"email"`
Domains []string `json:"domains,omitempty"`
CertPath string `json:"cert_path,omitempty" validate:"omitempty,filepath"`
KeyPath string `json:"key_path,omitempty" validate:"omitempty,filepath"`
ACMEKeyPath string `json:"acme_key_path,omitempty" validate:"omitempty,filepath"`
Provider string `json:"provider,omitempty"`
Options AutocertProviderOpt `json:"options,omitempty"`
}
AutocertProviderOpt map[string]any
)

View file

@ -4,21 +4,21 @@ import "github.com/yusing/go-proxy/internal/net/http/accesslog"
type (
Config struct {
AutoCert AutoCertConfig `json:"autocert" yaml:",flow"`
Entrypoint Entrypoint `json:"entrypoint" yaml:",flow"`
Providers Providers `json:"providers" yaml:",flow"`
MatchDomains []string `json:"match_domains" yaml:"match_domains"`
Homepage HomepageConfig `json:"homepage" yaml:"homepage"`
TimeoutShutdown int `json:"timeout_shutdown" yaml:"timeout_shutdown"`
AutoCert *AutoCertConfig `json:"autocert"`
Entrypoint Entrypoint `json:"entrypoint"`
Providers Providers `json:"providers"`
MatchDomains []string `json:"match_domains" validate:"dive,fqdn"`
Homepage HomepageConfig `json:"homepage"`
TimeoutShutdown int `json:"timeout_shutdown" validate:"gte=0"`
}
Providers struct {
Files []string `json:"include" yaml:"include"`
Docker map[string]string `json:"docker" yaml:"docker"`
Notification []NotificationConfig `json:"notification" yaml:"notification"`
Files []string `json:"include" validate:"dive,filepath"`
Docker map[string]string `json:"docker" validate:"dive,unix_addr|url"`
Notification []NotificationConfig `json:"notification"`
}
Entrypoint struct {
Middlewares []map[string]any `json:"middlewares" yaml:"middlewares"`
AccessLog *accesslog.Config `json:"access_log" yaml:"access_log"`
Middlewares []map[string]any `json:"middlewares"`
AccessLog *accesslog.Config `json:"access_log"`
}
NotificationConfig map[string]any
)

View file

@ -1,5 +1,5 @@
package types
type HomepageConfig struct {
UseDefaultCategories bool `json:"use_default_categories" yaml:"use_default_categories"`
UseDefaultCategories bool `json:"use_default_categories"`
}

View file

@ -15,29 +15,29 @@ type (
Container struct {
_ U.NoCopy
DockerHost string `json:"docker_host" yaml:"-"`
ContainerName string `json:"container_name" yaml:"-"`
ContainerID string `json:"container_id" yaml:"-"`
ImageName string `json:"image_name" yaml:"-"`
DockerHost string `json:"docker_host"`
ContainerName string `json:"container_name"`
ContainerID string `json:"container_id"`
ImageName string `json:"image_name"`
Labels map[string]string `json:"-" yaml:"-"`
Labels map[string]string `json:"-"`
PublicPortMapping PortMapping `json:"public_ports" yaml:"-"` // non-zero publicPort:types.Port
PrivatePortMapping PortMapping `json:"private_ports" yaml:"-"` // privatePort:types.Port
PublicIP string `json:"public_ip" yaml:"-"`
PrivateIP string `json:"private_ip" yaml:"-"`
NetworkMode string `json:"network_mode" yaml:"-"`
PublicPortMapping PortMapping `json:"public_ports"` // non-zero publicPort:types.Port
PrivatePortMapping PortMapping `json:"private_ports"` // privatePort:types.Port
PublicIP string `json:"public_ip"`
PrivateIP string `json:"private_ip"`
NetworkMode string `json:"network_mode"`
Aliases []string `json:"aliases" yaml:"-"`
IsExcluded bool `json:"is_excluded" yaml:"-"`
IsExplicit bool `json:"is_explicit" yaml:"-"`
IsDatabase bool `json:"is_database" yaml:"-"`
IdleTimeout string `json:"idle_timeout,omitempty" yaml:"-"`
WakeTimeout string `json:"wake_timeout,omitempty" yaml:"-"`
StopMethod string `json:"stop_method,omitempty" yaml:"-"`
StopTimeout string `json:"stop_timeout,omitempty" yaml:"-"` // stop_method = "stop" only
StopSignal string `json:"stop_signal,omitempty" yaml:"-"` // stop_method = "stop" | "kill" only
Running bool `json:"running" yaml:"-"`
Aliases []string `json:"aliases"`
IsExcluded bool `json:"is_excluded"`
IsExplicit bool `json:"is_explicit"`
IsDatabase bool `json:"is_database"`
IdleTimeout string `json:"idle_timeout,omitempty"`
WakeTimeout string `json:"wake_timeout,omitempty"`
StopMethod string `json:"stop_method,omitempty"`
StopTimeout string `json:"stop_timeout,omitempty"` // stop_method = "stop" only
StopSignal string `json:"stop_signal,omitempty"` // stop_method = "stop" | "kill" only
Running bool `json:"running"`
}
)

View file

@ -6,16 +6,16 @@ type (
Category []*Item
Item struct {
Show bool `json:"show" yaml:"show"`
Name string `json:"name" yaml:"name"`
Icon string `json:"icon" yaml:"icon"`
URL string `json:"url" yaml:"url"` // alias + domain
Category string `json:"category" yaml:"category"`
Description string `json:"description" yaml:"description" aliases:"desc"`
WidgetConfig map[string]any `json:"widget_config" yaml:",flow" aliases:"widget"`
Show bool `json:"show"`
Name string `json:"name"`
Icon string `json:"icon"`
URL string `json:"url"` // alias + domain
Category string `json:"category"`
Description string `json:"description" aliases:"desc"`
WidgetConfig map[string]any `json:"widget_config" aliases:"widget"`
SourceType string `json:"source_type" yaml:"-"`
AltURL string `json:"alt_url" yaml:"-"` // original proxy target
SourceType string `json:"source_type"`
AltURL string `json:"alt_url"` // original proxy target
}
)

View file

@ -2,6 +2,7 @@ package accesslog
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
@ -37,7 +38,7 @@ const logTimeFormat = "02/Jan/2006:15:04:05 -0700"
func NewFileAccessLogger(parent *task.Task, cfg *Config) (*AccessLogger, error) {
f, err := os.OpenFile(cfg.Path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return nil, err
return nil, fmt.Errorf("access log open error: %w", err)
}
return NewAccessLogger(parent, f, cfg), nil
}

View file

@ -1,6 +1,7 @@
package accesslog_test
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
@ -13,25 +14,11 @@ import (
. "github.com/yusing/go-proxy/internal/utils/testing"
)
type testWritter struct {
line string
}
func (w *testWritter) Write(p []byte) (n int, err error) {
w.line = string(p)
return len(p), nil
}
func (w *testWritter) Close() error {
return nil
}
var tw testWritter
const (
remote = "192.168.1.1"
u = "http://example.com/?bar=baz&foo=bar"
uRedacted = "http://example.com/?bar=" + RedactedValue + "&foo=" + RedactedValue
host = "example.com"
uri = "/?bar=baz&foo=bar"
uriRedacted = "/?bar=" + RedactedValue + "&foo=" + RedactedValue
referer = "https://www.google.com/"
proto = "HTTP/1.1"
ua = "Go-http-client/1.1"
@ -41,7 +28,7 @@ const (
)
var (
testURL = E.Must(url.Parse(u))
testURL = E.Must(url.Parse("http://" + host + uri))
req = &http.Request{
RemoteAddr: remote,
Method: method,
@ -62,17 +49,21 @@ var (
ContentLength: contentLength,
Header: http.Header{"Content-Type": []string{"text/plain"}},
}
task = taskPkg.GlobalTask("test logger")
)
func fmtLog(cfg *Config) string {
var line bytes.Buffer
logger := NewAccessLogger(taskPkg.GlobalTask("test logger"), nil, cfg)
logger.Format(&line, req, resp)
return line.String()
}
func TestAccessLoggerCommon(t *testing.T) {
config := DefaultConfig
config.Format = FormatCommon
logger := NewAccessLogger(task, &tw, &config)
logger.Log(req, resp)
ExpectEqual(t, tw.line,
fmt.Sprintf("%s - - [%s] \"%s %s %s\" %d %d\n",
remote, TestTimeNow, method, u, proto, status, contentLength,
ExpectEqual(t, fmtLog(&config),
fmt.Sprintf("%s %s - - [%s] \"%s %s %s\" %d %d",
host, remote, TestTimeNow, method, uri, proto, status, contentLength,
),
)
}
@ -80,11 +71,9 @@ func TestAccessLoggerCommon(t *testing.T) {
func TestAccessLoggerCombined(t *testing.T) {
config := DefaultConfig
config.Format = FormatCombined
logger := NewAccessLogger(task, &tw, &config)
logger.Log(req, resp)
ExpectEqual(t, tw.line,
fmt.Sprintf("%s - - [%s] \"%s %s %s\" %d %d \"%s\" \"%s\"\n",
remote, TestTimeNow, method, u, proto, status, contentLength, referer, ua,
ExpectEqual(t, fmtLog(&config),
fmt.Sprintf("%s %s - - [%s] \"%s %s %s\" %d %d \"%s\" \"%s\"",
host, remote, TestTimeNow, method, uri, proto, status, contentLength, referer, ua,
),
)
}
@ -93,11 +82,9 @@ func TestAccessLoggerRedactQuery(t *testing.T) {
config := DefaultConfig
config.Format = FormatCommon
config.Fields.Query.DefaultMode = FieldModeRedact
logger := NewAccessLogger(task, &tw, &config)
logger.Log(req, resp)
ExpectEqual(t, tw.line,
fmt.Sprintf("%s - - [%s] \"%s %s %s\" %d %d\n",
remote, TestTimeNow, method, uRedacted, proto, status, contentLength,
ExpectEqual(t, fmtLog(&config),
fmt.Sprintf("%s %s - - [%s] \"%s %s %s\" %d %d",
host, remote, TestTimeNow, method, uriRedacted, proto, status, contentLength,
),
)
}
@ -105,10 +92,8 @@ func TestAccessLoggerRedactQuery(t *testing.T) {
func getJSONEntry(t *testing.T, config *Config) JSONLogEntry {
t.Helper()
config.Format = FormatJSON
logger := NewAccessLogger(task, &tw, config)
logger.Log(req, resp)
var entry JSONLogEntry
err := json.Unmarshal([]byte(tw.line), &entry)
err := json.Unmarshal([]byte(fmtLog(config)), &entry)
ExpectNoError(t, err)
return entry
}

View file

@ -13,7 +13,7 @@ func TestNewConfig(t *testing.T) {
labels := map[string]string{
"proxy.buffer_size": "10",
"proxy.format": "combined",
"proxy.file_path": "/tmp/access.log",
"proxy.path": "/tmp/access.log",
"proxy.filters.status_codes.values": "200-299",
"proxy.filters.method.values": "GET, POST",
"proxy.filters.headers.values": "foo=bar, baz",

View file

@ -1,8 +1,8 @@
package types
type Config struct {
Link string `json:"link" yaml:"link"`
Mode Mode `json:"mode" yaml:"mode"`
Weight Weight `json:"weight" yaml:"weight"`
Options map[string]any `json:"options,omitempty" yaml:"options,omitempty"`
Link string `json:"link"`
Mode Mode `json:"mode"`
Weight Weight `json:"weight"`
Options map[string]any `json:"options,omitempty"`
}

View file

@ -8,7 +8,7 @@ import (
"github.com/yusing/go-proxy/internal/common"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/route"
U "github.com/yusing/go-proxy/internal/utils"
"github.com/yusing/go-proxy/internal/utils"
W "github.com/yusing/go-proxy/internal/watcher"
)
@ -31,8 +31,9 @@ func FileProviderImpl(filename string) (ProviderImpl, error) {
return impl, nil
}
func Validate(data []byte) E.Error {
return U.ValidateYaml(U.GetSchema(common.FileProviderSchemaPath), data)
func Validate(data []byte) (err E.Error) {
_, err = utils.DeserializeYAMLMap[*route.RawEntry](data)
return
}
func (p *FileProvider) String() string {
@ -45,22 +46,17 @@ func (p *FileProvider) Logger() *zerolog.Logger {
func (p *FileProvider) loadRoutesImpl() (route.Routes, E.Error) {
routes := route.NewRoutes()
entries := route.NewProxyEntries()
data, err := os.ReadFile(p.path)
if err != nil {
return routes, E.From(err)
}
if err := entries.UnmarshalFromYAML(data); err != nil {
return routes, E.From(err)
entries, err := utils.DeserializeYAMLMap[*route.RawEntry](data)
if err == nil {
return route.FromEntries(entries)
}
if err := Validate(data); err != nil {
E.LogWarn("validation failure", err.Subject(p.fileName))
}
return route.FromEntries(entries)
return routes, E.From(err)
}
func (p *FileProvider) NewWatcher() W.Watcher {

View file

@ -16,7 +16,7 @@ import (
"github.com/yusing/go-proxy/internal/watcher/health/monitor"
)
// TODO: support stream load balance
// TODO: support stream load balance.
type StreamRoute struct {
*entry.StreamEntry

View file

@ -24,20 +24,20 @@ type (
// raw entry object before validation
// loaded from docker labels or yaml file
Alias string `json:"-" yaml:"-"`
Scheme string `json:"scheme,omitempty" yaml:"scheme"`
Host string `json:"host,omitempty" yaml:"host"`
Port string `json:"port,omitempty" yaml:"port"`
NoTLSVerify bool `json:"no_tls_verify,omitempty" yaml:"no_tls_verify"` // https proxy only
PathPatterns []string `json:"path_patterns,omitempty" yaml:"path_patterns"` // http(s) proxy only
HealthCheck *health.HealthCheckConfig `json:"healthcheck,omitempty" yaml:"healthcheck"`
LoadBalance *loadbalance.Config `json:"load_balance,omitempty" yaml:"load_balance"`
Middlewares map[string]docker.LabelMap `json:"middlewares,omitempty" yaml:"middlewares"`
Homepage *homepage.Item `json:"homepage,omitempty" yaml:"homepage"`
AccessLog *accesslog.Config `json:"access_log,omitempty" yaml:"access_log"`
Alias string `json:"-"`
Scheme string `json:"scheme,omitempty"`
Host string `json:"host,omitempty"`
Port string `json:"port,omitempty"`
NoTLSVerify bool `json:"no_tls_verify,omitempty"`
PathPatterns []string `json:"path_patterns,omitempty"`
HealthCheck *health.HealthCheckConfig `json:"healthcheck,omitempty"`
LoadBalance *loadbalance.Config `json:"load_balance,omitempty"`
Middlewares map[string]docker.LabelMap `json:"middlewares,omitempty"`
Homepage *homepage.Item `json:"homepage,omitempty"`
AccessLog *accesslog.Config `json:"access_log,omitempty"`
/* Docker only */
Container *docker.Container `json:"container,omitempty" yaml:"-"`
Container *docker.Container `json:"container,omitempty"`
finalized bool
}

View file

@ -1,7 +1,6 @@
package functional
import (
"errors"
"sync"
"github.com/puzpuzpuz/xsync/v3"
@ -195,31 +194,6 @@ 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) error {
if m.Size() != 0 {
return errors.New("cannot unmarshal into non-empty map")
}
tmp := make(map[KT]VT)
if err := yaml.Unmarshal(data, tmp); err != nil {
return err
}
for k, v := range tmp {
m.Store(k, v)
}
return nil
}
func (m Map[KT, VT]) String() string {
tmp := make(map[KT]VT, m.Size())
m.RangeAll(func(k KT, v VT) {

View file

@ -2,10 +2,8 @@ package utils
import (
"context"
"encoding/json"
"errors"
"io"
"os"
"sync"
"syscall"
@ -151,19 +149,3 @@ func Copy(dst *ContextWriter, src *ContextReader) (err error) {
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) error {
data, err := os.ReadFile(path)
if err != nil {
return err
}
return json.Unmarshal(data, pointer)
}
func SaveJSON[T any](path string, pointer *T, perm os.FileMode) error {
data, err := json.Marshal(pointer)
if err != nil {
return err
}
return os.WriteFile(path, data, perm)
}

View file

@ -1,27 +0,0 @@
package utils
import (
"sync"
"github.com/santhosh-tekuri/jsonschema"
)
var (
schemaCompiler = jsonschema.NewCompiler()
schemaStorage = make(map[string]*jsonschema.Schema)
schemaMu sync.Mutex
)
func GetSchema(path string) *jsonschema.Schema {
if schema, ok := schemaStorage[path]; ok {
return schema
}
schemaMu.Lock()
defer schemaMu.Unlock()
if schema, ok := schemaStorage[path]; ok {
return schema
}
schema := schemaCompiler.MustCompile(path)
schemaStorage[path] = schema
return schema
}

View file

@ -1,10 +1,10 @@
package utils
import (
"bytes"
"encoding/json"
"errors"
"net"
"os"
"reflect"
"strconv"
"strings"
@ -12,8 +12,8 @@ import (
"unicode"
"github.com/go-playground/validator/v10"
"github.com/santhosh-tekuri/jsonschema"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/utils/functional"
"github.com/yusing/go-proxy/internal/utils/strutils"
"gopkg.in/yaml.v3"
)
@ -30,36 +30,6 @@ var (
ErrUnknownField = E.New("unknown field")
)
func ValidateYaml(schema *jsonschema.Schema, data []byte) E.Error {
var i any
err := yaml.Unmarshal(data, &i)
if err != nil {
return E.From(err)
}
m, err := json.Marshal(i)
if err != nil {
return E.From(err)
}
err = schema.Validate(bytes.NewReader(m))
if err == nil {
return nil
}
var valErr *jsonschema.ValidationError
if !errors.As(err, &valErr) {
panic(err)
}
b := E.NewBuilder("yaml validation error")
for _, e := range valErr.Causes {
b.Adds(e.Message)
}
return b.Error()
}
// 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.
@ -482,10 +452,38 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr E.E
return true, Convert(reflect.ValueOf(tmp), dst)
}
func DeserializeJSON(j map[string]string, target any) error {
data, err := json.Marshal(j)
func DeserializeYAML[T any](data []byte, target T) E.Error {
m := make(map[string]any)
if err := yaml.Unmarshal(data, m); err != nil {
return E.From(err)
}
return Deserialize(m, target)
}
func DeserializeYAMLMap[V any](data []byte) (_ functional.Map[string, V], err E.Error) {
m := make(map[string]any)
if err = E.From(yaml.Unmarshal(data, m)); err != nil {
return
}
m2 := make(map[string]V, len(m))
if err = Deserialize(m, m2); err != nil {
return
}
return functional.NewMapFrom(m2), nil
}
func LoadJSON[T any](path string, dst *T) error {
data, err := os.ReadFile(path)
if err != nil {
return err
}
return json.Unmarshal(data, target)
return json.Unmarshal(data, dst)
}
func SaveJSON[T any](path string, src *T, perm os.FileMode) error {
data, err := json.Marshal(src)
if err != nil {
return err
}
return os.WriteFile(path, data, perm)
}

View file

@ -5,9 +5,9 @@ import (
)
type HealthCheckConfig struct {
Disable bool `json:"disable,omitempty" yaml:"disable" aliases:"disabled"`
Path string `json:"path,omitempty" yaml:"path"`
UseGet bool `json:"use_get,omitempty" yaml:"use_get"`
Interval time.Duration `json:"interval" yaml:"interval"`
Timeout time.Duration `json:"timeout" yaml:"timeout"`
Disable bool `json:"disable,omitempty" aliases:"disabled"`
Path string `json:"path,omitempty" validate:"omitempty,uri,startswith=/"`
UseGet bool `json:"use_get,omitempty"`
Interval time.Duration `json:"interval" validate:"omitempty,min=1s"`
Timeout time.Duration `json:"timeout" validate:"omitempty,min=1s"`
}