diff --git a/Dockerfile b/Dockerfile index 1ff874f..9d6dae2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/go.mod b/go.mod index c1221be..a4dd3e6 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 2ffd6ff..afb87bf 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/autocert/config.go b/internal/autocert/config.go index c63096e..d8682b4 100644 --- a/internal/autocert/config.go +++ b/internal/autocert/config.go @@ -27,6 +27,9 @@ var ( ) func NewConfig(cfg *types.AutoCertConfig) *Config { + if cfg == nil { + cfg = new(types.AutoCertConfig) + } if cfg.CertPath == "" { cfg.CertPath = CertFileDefault } diff --git a/internal/common/env.go b/internal/common/env.go index 9ee7277..2d8da6c 100644 --- a/internal/common/env.go +++ b/internal/common/env.go @@ -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, diff --git a/internal/config/config.go b/internal/config/config.go index 924f382..cf9001f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -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 diff --git a/internal/config/types/autocert_config.go b/internal/config/types/autocert_config.go index b656403..f054d01 100644 --- a/internal/config/types/autocert_config.go +++ b/internal/config/types/autocert_config.go @@ -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 ) diff --git a/internal/config/types/config.go b/internal/config/types/config.go index 39bd12c..1130dcf 100644 --- a/internal/config/types/config.go +++ b/internal/config/types/config.go @@ -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 ) diff --git a/internal/config/types/homepage_config.go b/internal/config/types/homepage_config.go index e44c636..14301c8 100644 --- a/internal/config/types/homepage_config.go +++ b/internal/config/types/homepage_config.go @@ -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"` } diff --git a/internal/docker/container.go b/internal/docker/container.go index f17cdbf..129d289 100644 --- a/internal/docker/container.go +++ b/internal/docker/container.go @@ -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"` } ) diff --git a/internal/homepage/homepage.go b/internal/homepage/homepage.go index 03ccf6f..45c4c32 100644 --- a/internal/homepage/homepage.go +++ b/internal/homepage/homepage.go @@ -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 } ) diff --git a/internal/net/http/accesslog/access_logger.go b/internal/net/http/accesslog/access_logger.go index b9d35fa..b16284e 100644 --- a/internal/net/http/accesslog/access_logger.go +++ b/internal/net/http/accesslog/access_logger.go @@ -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 } diff --git a/internal/net/http/accesslog/access_logger_test.go b/internal/net/http/accesslog/access_logger_test.go index f44b776..2e9cc9d 100644 --- a/internal/net/http/accesslog/access_logger_test.go +++ b/internal/net/http/accesslog/access_logger_test.go @@ -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 } diff --git a/internal/net/http/accesslog/config_test.go b/internal/net/http/accesslog/config_test.go index 4706410..f7e0989 100644 --- a/internal/net/http/accesslog/config_test.go +++ b/internal/net/http/accesslog/config_test.go @@ -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", diff --git a/internal/net/http/loadbalancer/types/config.go b/internal/net/http/loadbalancer/types/config.go index 4939188..8e1c38c 100644 --- a/internal/net/http/loadbalancer/types/config.go +++ b/internal/net/http/loadbalancer/types/config.go @@ -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"` } diff --git a/internal/route/provider/file.go b/internal/route/provider/file.go index ffa1788..12c7257 100644 --- a/internal/route/provider/file.go +++ b/internal/route/provider/file.go @@ -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 { diff --git a/internal/route/stream.go b/internal/route/stream.go index eb230b4..b8bb1b7 100755 --- a/internal/route/stream.go +++ b/internal/route/stream.go @@ -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 diff --git a/internal/route/types/raw_entry.go b/internal/route/types/raw_entry.go index 5c0a0b2..ce7796d 100644 --- a/internal/route/types/raw_entry.go +++ b/internal/route/types/raw_entry.go @@ -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 } diff --git a/internal/utils/functional/map.go b/internal/utils/functional/map.go index 8d95b17..bac733a 100644 --- a/internal/utils/functional/map.go +++ b/internal/utils/functional/map.go @@ -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) { diff --git a/internal/utils/io.go b/internal/utils/io.go index d70e168..0e65c46 100644 --- a/internal/utils/io.go +++ b/internal/utils/io.go @@ -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) -} diff --git a/internal/utils/schema.go b/internal/utils/schema.go deleted file mode 100644 index 0ca0099..0000000 --- a/internal/utils/schema.go +++ /dev/null @@ -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 -} diff --git a/internal/utils/serialization.go b/internal/utils/serialization.go index 24ad7a2..de7e53e 100644 --- a/internal/utils/serialization.go +++ b/internal/utils/serialization.go @@ -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) } diff --git a/internal/watcher/health/config.go b/internal/watcher/health/config.go index fda787d..4896e3e 100644 --- a/internal/watcher/health/config.go +++ b/internal/watcher/health/config.go @@ -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"` }