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 binary
COPY --from=builder /app /app COPY --from=builder /app /app
# copy schema directory
COPY schema/ /app/schema/
# copy example config # copy example config
COPY config.example.yml /app/config/config.yml 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/prometheus/client_golang v1.20.5
github.com/puzpuzpuz/xsync/v3 v3.4.0 github.com/puzpuzpuz/xsync/v3 v3.4.0
github.com/rs/zerolog v1.33.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/net v0.32.0
golang.org/x/text v0.21.0 golang.org/x/text v0.21.0
golang.org/x/time v0.8.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/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 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= 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 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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 { func NewConfig(cfg *types.AutoCertConfig) *Config {
if cfg == nil {
cfg = new(types.AutoCertConfig)
}
if cfg.CertPath == "" { if cfg.CertPath == "" {
cfg.CertPath = CertFileDefault cfg.CertPath = CertFileDefault
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,6 @@
package functional package functional
import ( import (
"errors"
"sync" "sync"
"github.com/puzpuzpuz/xsync/v3" "github.com/puzpuzpuz/xsync/v3"
@ -195,31 +194,6 @@ func (m Map[KT, VT]) Has(k KT) bool {
return ok 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 { func (m Map[KT, VT]) String() string {
tmp := make(map[KT]VT, m.Size()) tmp := make(map[KT]VT, m.Size())
m.RangeAll(func(k KT, v VT) { m.RangeAll(func(k KT, v VT) {

View file

@ -2,10 +2,8 @@ package utils
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"io" "io"
"os"
"sync" "sync"
"syscall" "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 { func Copy2(ctx context.Context, dst io.Writer, src io.Reader) error {
return Copy(&ContextWriter{ctx: ctx, Writer: dst}, &ContextReader{ctx: ctx, Reader: src}) 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 package utils
import ( import (
"bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"net" "net"
"os"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
@ -12,8 +12,8 @@ import (
"unicode" "unicode"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"github.com/santhosh-tekuri/jsonschema"
E "github.com/yusing/go-proxy/internal/error" E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/utils/functional"
"github.com/yusing/go-proxy/internal/utils/strutils" "github.com/yusing/go-proxy/internal/utils/strutils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -30,36 +30,6 @@ var (
ErrUnknownField = E.New("unknown field") 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. // 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. // 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) return true, Convert(reflect.ValueOf(tmp), dst)
} }
func DeserializeJSON(j map[string]string, target any) error { func DeserializeYAML[T any](data []byte, target T) E.Error {
data, err := json.Marshal(j) 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 { if err != nil {
return err 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 { type HealthCheckConfig struct {
Disable bool `json:"disable,omitempty" yaml:"disable" aliases:"disabled"` Disable bool `json:"disable,omitempty" aliases:"disabled"`
Path string `json:"path,omitempty" yaml:"path"` Path string `json:"path,omitempty" validate:"omitempty,uri,startswith=/"`
UseGet bool `json:"use_get,omitempty" yaml:"use_get"` UseGet bool `json:"use_get,omitempty"`
Interval time.Duration `json:"interval" yaml:"interval"` Interval time.Duration `json:"interval" validate:"omitempty,min=1s"`
Timeout time.Duration `json:"timeout" yaml:"timeout"` Timeout time.Duration `json:"timeout" validate:"omitempty,min=1s"`
} }