mirror of
https://github.com/aykhans/dodo.git
synced 2025-09-07 11:30:47 +00:00
add config file parser
This commit is contained in:
@@ -36,7 +36,6 @@ linters:
|
||||
- embeddedstructfieldcheck
|
||||
- errchkjson
|
||||
- errorlint
|
||||
- exhaustive
|
||||
- exptostd
|
||||
- fatcontext
|
||||
- forcetypeassert
|
||||
@@ -66,7 +65,6 @@ linters:
|
||||
- nilerr
|
||||
- nilnesserr
|
||||
- nilnil
|
||||
- noctx
|
||||
- nonamedreturns
|
||||
- nosprintfhostport
|
||||
- perfsprint
|
||||
@@ -99,6 +97,7 @@ linters:
|
||||
varnamelen:
|
||||
ignore-decls:
|
||||
- i int
|
||||
- w http.ResponseWriter
|
||||
|
||||
exclusions:
|
||||
rules:
|
||||
|
@@ -1,61 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/aykhans/dodo/pkg/config/parser"
|
||||
"github.com/aykhans/dodo/pkg/types"
|
||||
"github.com/aykhans/dodo/pkg/utils"
|
||||
"github.com/jedib0t/go-pretty/v6/text"
|
||||
"github.com/aykhans/dodo/pkg/config"
|
||||
"github.com/k0kubun/pp/v3"
|
||||
)
|
||||
|
||||
func main() {
|
||||
envParser := parser.NewConfigENVParser("DODO")
|
||||
envConfig, err := envParser.Parse()
|
||||
_ = utils.HandleErrorOrDie(err,
|
||||
utils.OnCustomError(func(err types.FieldParseErrors) error {
|
||||
printValidationErrors("ENV", err.Errors...)
|
||||
fmt.Println()
|
||||
os.Exit(1)
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
|
||||
cliParser := parser.NewConfigCLIParser(os.Args)
|
||||
cliConf, err := cliParser.Parse()
|
||||
_ = utils.HandleErrorOrDie(err,
|
||||
utils.OnSentinelError(types.ErrCLINoArgs, func(err error) error {
|
||||
cliParser.PrintHelp()
|
||||
utils.PrintErrAndExit(text.FgRed, 1, "\nNo arguments provided.")
|
||||
return nil
|
||||
}),
|
||||
utils.OnCustomError(func(err types.CLIUnexpectedArgsError) error {
|
||||
cliParser.PrintHelp()
|
||||
utils.PrintErrAndExit(text.FgRed, 1, "\nUnexpected CLI arguments provided: %v", err.Args)
|
||||
return nil
|
||||
}),
|
||||
utils.OnCustomError(func(err types.FieldParseErrors) error {
|
||||
cliParser.PrintHelp()
|
||||
fmt.Println()
|
||||
printValidationErrors("CLI", err.Errors...)
|
||||
fmt.Println()
|
||||
os.Exit(1)
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
|
||||
envConfig.Merge(cliConf)
|
||||
pp.Println(cliConf) //nolint
|
||||
pp.Println(envConfig) //nolint
|
||||
}
|
||||
|
||||
func printValidationErrors(parserName string, errors ...types.FieldParseError) {
|
||||
for _, fieldErr := range errors {
|
||||
if fieldErr.Value == "" {
|
||||
utils.PrintErr(text.FgYellow, "[%s] Field '%s': %v", parserName, fieldErr.Field, fieldErr.Err)
|
||||
}
|
||||
utils.PrintErr(text.FgYellow, "[%s] Field '%s' (%s): %v", parserName, fieldErr.Field, fieldErr.Value, fieldErr.Err)
|
||||
}
|
||||
cfg := config.ReadAllConfigs()
|
||||
pp.Println(cfg) //nolint
|
||||
}
|
||||
|
1
go.mod
1
go.mod
@@ -6,6 +6,7 @@ require (
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.8
|
||||
github.com/k0kubun/pp/v3 v3.5.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.2
|
||||
)
|
||||
|
||||
require (
|
||||
|
2
go.sum
2
go.sum
@@ -17,6 +17,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package parser
|
||||
package config
|
||||
|
||||
import (
|
||||
"flag"
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aykhans/dodo/pkg/config"
|
||||
"github.com/aykhans/dodo/pkg/types"
|
||||
"github.com/aykhans/dodo/pkg/utils"
|
||||
)
|
||||
@@ -81,13 +80,13 @@ func (arg *stringSliceArg) Set(value string) error {
|
||||
// - types.ErrCLINoArgs
|
||||
// - types.CLIUnexpectedArgsError
|
||||
// - types.FieldParseErrors
|
||||
func (parser ConfigCLIParser) Parse() (*config.Config, error) {
|
||||
func (parser ConfigCLIParser) Parse() (*Config, error) {
|
||||
flagSet := flag.NewFlagSet("dodo", flag.ExitOnError)
|
||||
|
||||
flagSet.Usage = func() { parser.PrintHelp() }
|
||||
|
||||
var (
|
||||
config = &config.Config{}
|
||||
config = &Config{}
|
||||
configFiles = stringSliceArg{}
|
||||
yes bool
|
||||
skipVerify bool
|
||||
@@ -242,11 +241,11 @@ func (parser ConfigCLIParser) Parse() (*config.Config, error) {
|
||||
func (parser ConfigCLIParser) PrintHelp() {
|
||||
fmt.Printf(
|
||||
cliUsageText+"\n",
|
||||
config.Defaults.Yes,
|
||||
config.Defaults.DodosCount,
|
||||
config.Defaults.RequestTimeout,
|
||||
config.Defaults.Method,
|
||||
config.Defaults.SkipVerify,
|
||||
Defaults.Yes,
|
||||
Defaults.DodosCount,
|
||||
Defaults.RequestTimeout,
|
||||
Defaults.Method,
|
||||
Defaults.SkipVerify,
|
||||
)
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package parser
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aykhans/dodo/pkg/config"
|
||||
"github.com/aykhans/dodo/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -479,7 +478,7 @@ func TestConfigCLIParser_PrintHelp(t *testing.T) {
|
||||
assert.Contains(t, output, "-f, -config-file")
|
||||
|
||||
// Verify default values are included
|
||||
assert.Contains(t, output, config.Defaults.Method)
|
||||
assert.Contains(t, output, Defaults.Method)
|
||||
assert.Contains(t, output, "1") // DodosCount default
|
||||
assert.Contains(t, output, "10s") // RequestTimeout default
|
||||
assert.Contains(t, output, "false") // Yes default
|
@@ -1,11 +1,14 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/aykhans/dodo/pkg/types"
|
||||
"github.com/aykhans/dodo/pkg/utils"
|
||||
"github.com/jedib0t/go-pretty/v6/text"
|
||||
)
|
||||
|
||||
const VERSION string = "1.0.0"
|
||||
@@ -26,7 +29,11 @@ var Defaults = struct {
|
||||
SkipVerify: false,
|
||||
}
|
||||
|
||||
var SupportedProxySchemes []string = []string{"http", "socks5", "socks5h"}
|
||||
var SupportedProxySchemes = []string{"http", "socks5", "socks5h"}
|
||||
|
||||
type IParser interface {
|
||||
Parse() (*Config, error)
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Files []types.ConfigFile
|
||||
@@ -112,3 +119,102 @@ func (config *Config) SetDefaults() {
|
||||
config.Headers = append(config.Headers, types.Header{Key: "User-Agent", Value: []string{Defaults.UserAgent}})
|
||||
}
|
||||
}
|
||||
|
||||
func ReadAllConfigs() *Config {
|
||||
envParser := NewConfigENVParser("DODO")
|
||||
envConfig, err := envParser.Parse()
|
||||
_ = utils.HandleErrorOrDie(err,
|
||||
utils.OnCustomError(func(err types.FieldParseErrors) error {
|
||||
printValidationErrors("ENV", err.Errors...)
|
||||
fmt.Println()
|
||||
os.Exit(1)
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
|
||||
cliParser := NewConfigCLIParser(os.Args)
|
||||
cliConf, err := cliParser.Parse()
|
||||
_ = utils.HandleErrorOrDie(err,
|
||||
utils.OnSentinelError(types.ErrCLINoArgs, func(err error) error {
|
||||
cliParser.PrintHelp()
|
||||
utils.PrintErrAndExit(text.FgYellow, 1, "\nNo arguments provided.")
|
||||
return nil
|
||||
}),
|
||||
utils.OnCustomError(func(err types.CLIUnexpectedArgsError) error {
|
||||
cliParser.PrintHelp()
|
||||
utils.PrintErrAndExit(text.FgYellow, 1, "\nUnexpected CLI arguments provided: %v", err.Args)
|
||||
return nil
|
||||
}),
|
||||
utils.OnCustomError(func(err types.FieldParseErrors) error {
|
||||
cliParser.PrintHelp()
|
||||
fmt.Println()
|
||||
printValidationErrors("CLI", err.Errors...)
|
||||
os.Exit(1)
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
|
||||
envConfig.Merge(cliConf)
|
||||
|
||||
for _, configFile := range envConfig.Files {
|
||||
fileConfig, err := parseConfigFile(configFile, 10)
|
||||
_ = utils.HandleErrorOrDie(err,
|
||||
utils.OnCustomError(func(err types.ConfigFileReadError) error {
|
||||
cliParser.PrintHelp()
|
||||
utils.PrintErrAndExit(text.FgYellow, 1, "\nFailed to read config file '%s': %v", configFile.Path(), err)
|
||||
return nil
|
||||
}),
|
||||
utils.OnCustomError(func(err types.UnmarshalError) error {
|
||||
utils.PrintErrAndExit(text.FgYellow, 1, "\nFailed to unmarshal config file '%s': %v", configFile.Path(), err)
|
||||
return nil
|
||||
}),
|
||||
utils.OnCustomError(func(err types.FieldParseErrors) error {
|
||||
printValidationErrors(fmt.Sprintf("CONFIG FILE '%s'", configFile.Path()), err.Errors...)
|
||||
os.Exit(1)
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
|
||||
envConfig.Merge(fileConfig)
|
||||
}
|
||||
|
||||
return envConfig
|
||||
}
|
||||
|
||||
// parseConfigFile recursively parses a config file and its nested files up to maxDepth levels.
|
||||
// Returns the merged configuration or an error if parsing fails.
|
||||
// It can return the following errors:
|
||||
// - types.ConfigFileReadError
|
||||
// - types.UnmarshalError
|
||||
// - types.FieldParseErrors
|
||||
func parseConfigFile(configFile types.ConfigFile, maxDepth int) (*Config, error) {
|
||||
configFileParser := NewConfigFileParser(configFile)
|
||||
fileConfig, err := configFileParser.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if maxDepth <= 0 {
|
||||
return fileConfig, nil
|
||||
}
|
||||
|
||||
for _, c := range fileConfig.Files {
|
||||
innerFileConfig, err := parseConfigFile(c, maxDepth-1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fileConfig.Merge(innerFileConfig)
|
||||
}
|
||||
|
||||
return fileConfig, nil
|
||||
}
|
||||
|
||||
func printValidationErrors(parserName string, errors ...types.FieldParseError) {
|
||||
for _, fieldErr := range errors {
|
||||
if fieldErr.Value == "" {
|
||||
utils.PrintErr(text.FgYellow, "[%s] Field '%s': %v", parserName, fieldErr.Field, fieldErr.Err)
|
||||
}
|
||||
utils.PrintErr(text.FgYellow, "[%s] Field '%s' (%s): %v", parserName, fieldErr.Field, fieldErr.Value, fieldErr.Err)
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -525,3 +526,83 @@ func TestMergeConfig_AppendBehavior(t *testing.T) {
|
||||
assert.Len(t, config.Proxies, originalProxies, "Empty proxies should not change existing proxies")
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseConfigFile(t *testing.T) {
|
||||
t.Run("parseConfigFile with maxDepth 0", func(t *testing.T) {
|
||||
// Create a mock config file
|
||||
configFile, _ := types.ParseConfigFile("test.yaml")
|
||||
|
||||
// Since we can't actually test file reading without a real file,
|
||||
// we'll test the function's behavior with maxDepth
|
||||
config, err := parseConfigFile(*configFile, 0)
|
||||
|
||||
// The function will return an error because the file doesn't exist
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, config)
|
||||
})
|
||||
|
||||
t.Run("parseConfigFile returns ConfigFileReadError", func(t *testing.T) {
|
||||
configFile, _ := types.ParseConfigFile("/nonexistent/file.yaml")
|
||||
config, err := parseConfigFile(*configFile, 1)
|
||||
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, config)
|
||||
// Check if error is of type ConfigFileReadError
|
||||
var readErr types.ConfigFileReadError
|
||||
assert.ErrorAs(t, err, &readErr)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPrintValidationErrors(t *testing.T) {
|
||||
t.Run("printValidationErrors with empty value", func(t *testing.T) {
|
||||
// This function prints to stdout, so we can't easily test its output
|
||||
// But we can test that it doesn't panic
|
||||
errors := []types.FieldParseError{
|
||||
{
|
||||
Field: "test_field",
|
||||
Value: "",
|
||||
Err: errors.New("test error"),
|
||||
},
|
||||
}
|
||||
|
||||
// Should not panic
|
||||
assert.NotPanics(t, func() {
|
||||
printValidationErrors("TEST", errors...)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("printValidationErrors with value", func(t *testing.T) {
|
||||
errors := []types.FieldParseError{
|
||||
{
|
||||
Field: "test_field",
|
||||
Value: "test_value",
|
||||
Err: errors.New("test error"),
|
||||
},
|
||||
}
|
||||
|
||||
// Should not panic
|
||||
assert.NotPanics(t, func() {
|
||||
printValidationErrors("TEST", errors...)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("printValidationErrors with multiple errors", func(t *testing.T) {
|
||||
errors := []types.FieldParseError{
|
||||
{
|
||||
Field: "field1",
|
||||
Value: "value1",
|
||||
Err: errors.New("error1"),
|
||||
},
|
||||
{
|
||||
Field: "field2",
|
||||
Value: "",
|
||||
Err: errors.New("error2"),
|
||||
},
|
||||
}
|
||||
|
||||
// Should not panic
|
||||
assert.NotPanics(t, func() {
|
||||
printValidationErrors("TEST", errors...)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package parser
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/aykhans/dodo/pkg/config"
|
||||
"github.com/aykhans/dodo/pkg/types"
|
||||
"github.com/aykhans/dodo/pkg/utils"
|
||||
)
|
||||
@@ -25,9 +24,9 @@ func NewConfigENVParser(envPrefix string) *ConfigENVParser {
|
||||
// Parse parses env arguments into a Config object.
|
||||
// It can return the following errors:
|
||||
// - types.FieldParseErrors
|
||||
func (parser ConfigENVParser) Parse() (*config.Config, error) {
|
||||
func (parser ConfigENVParser) Parse() (*Config, error) {
|
||||
var (
|
||||
config = &config.Config{}
|
||||
config = &Config{}
|
||||
fieldParseErrors []types.FieldParseError
|
||||
)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package parser
|
||||
package config
|
||||
|
||||
import (
|
||||
"net/url"
|
184
pkg/config/file.go
Normal file
184
pkg/config/file.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/aykhans/dodo/pkg/types"
|
||||
"github.com/aykhans/dodo/pkg/utils"
|
||||
"go.yaml.in/yaml/v4"
|
||||
)
|
||||
|
||||
var _ IParser = ConfigFileParser{}
|
||||
|
||||
type ConfigFileParser struct {
|
||||
configFile types.ConfigFile
|
||||
}
|
||||
|
||||
func NewConfigFileParser(configFile types.ConfigFile) *ConfigFileParser {
|
||||
return &ConfigFileParser{configFile}
|
||||
}
|
||||
|
||||
// Parse parses config file arguments into a Config object.
|
||||
// It can return the following errors:
|
||||
// - types.ConfigFileReadError
|
||||
// - types.UnmarshalError
|
||||
// - types.FieldParseErrors
|
||||
func (parser ConfigFileParser) Parse() (*Config, error) {
|
||||
var err error
|
||||
var configFileData []byte
|
||||
|
||||
switch parser.configFile.LocationType() {
|
||||
case types.ConfigFileLocationLocal:
|
||||
configFileData, err = os.ReadFile(parser.configFile.Path())
|
||||
if err != nil {
|
||||
return nil, types.NewConfigFileReadError(err)
|
||||
}
|
||||
case types.ConfigFileLocationRemote:
|
||||
resp, err := http.Get(parser.configFile.Path())
|
||||
if err != nil {
|
||||
return nil, types.NewConfigFileReadError(err)
|
||||
}
|
||||
defer resp.Body.Close() //nolint:errcheck
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, types.NewConfigFileReadError(errors.New("failed to retrieve remote config file: " + resp.Status))
|
||||
}
|
||||
|
||||
configFileData, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, types.NewConfigFileReadError(err)
|
||||
}
|
||||
default:
|
||||
panic("unhandled config file location type")
|
||||
}
|
||||
|
||||
switch parser.configFile.Type() {
|
||||
case types.ConfigFileTypeYAML, types.ConfigFileTypeUnknown:
|
||||
return parser.ParseYAML(configFileData)
|
||||
default:
|
||||
panic("unhandled config file type")
|
||||
}
|
||||
}
|
||||
|
||||
type stringOrSliceField []string
|
||||
|
||||
func (ss *stringOrSliceField) UnmarshalYAML(node *yaml.Node) error {
|
||||
switch node.Kind {
|
||||
case yaml.ScalarNode:
|
||||
// Handle single string value
|
||||
*ss = []string{node.Value}
|
||||
return nil
|
||||
case yaml.SequenceNode:
|
||||
// Handle array of strings
|
||||
var slice []string
|
||||
if err := node.Decode(&slice); err != nil {
|
||||
return err //nolint:wrapcheck
|
||||
}
|
||||
*ss = slice
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("expected a string or a sequence of strings, but got %v", node.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
type configYAML struct {
|
||||
Files stringOrSliceField `yaml:"files"`
|
||||
Method *string `yaml:"method"`
|
||||
URL *string `yaml:"url"`
|
||||
Timeout *time.Duration `yaml:"timeout"`
|
||||
DodosCount *uint `yaml:"dodos"`
|
||||
RequestCount *uint `yaml:"requests"`
|
||||
Duration *time.Duration `yaml:"duration"`
|
||||
Yes *bool `yaml:"yes"`
|
||||
SkipVerify *bool `yaml:"skipVerify"`
|
||||
Params stringOrSliceField `yaml:"params"`
|
||||
Headers stringOrSliceField `yaml:"headers"`
|
||||
Cookies stringOrSliceField `yaml:"cookies"`
|
||||
Bodies stringOrSliceField `yaml:"body"`
|
||||
Proxies stringOrSliceField `yaml:"proxy"`
|
||||
}
|
||||
|
||||
// ParseYAML parses YAML config file arguments into a Config object.
|
||||
// It can return the following errors:
|
||||
// - types.UnmarshalError
|
||||
// - types.FieldParseErrors
|
||||
func (parser ConfigFileParser) ParseYAML(data []byte) (*Config, error) {
|
||||
var (
|
||||
config = &Config{}
|
||||
parsedData = &configYAML{}
|
||||
)
|
||||
|
||||
err := yaml.Unmarshal(data, &parsedData)
|
||||
if err != nil {
|
||||
return nil, types.NewUnmarshalError(err)
|
||||
}
|
||||
|
||||
var fieldParseErrors []types.FieldParseError
|
||||
|
||||
config.Method = parsedData.Method
|
||||
config.Timeout = parsedData.Timeout
|
||||
config.DodosCount = parsedData.DodosCount
|
||||
config.RequestCount = parsedData.RequestCount
|
||||
config.Duration = parsedData.Duration
|
||||
config.Yes = parsedData.Yes
|
||||
config.SkipVerify = parsedData.SkipVerify
|
||||
config.Params.Parse(parsedData.Params...)
|
||||
config.Headers.Parse(parsedData.Headers...)
|
||||
config.Cookies.Parse(parsedData.Cookies...)
|
||||
config.Bodies.Parse(parsedData.Bodies...)
|
||||
|
||||
if len(parsedData.Files) > 0 {
|
||||
for i, configFile := range parsedData.Files {
|
||||
configFileParsed, err := types.ParseConfigFile(configFile)
|
||||
|
||||
_ = utils.HandleErrorOrDie(err,
|
||||
utils.OnCustomError(func(err types.RemoteConfigFileParseError) error {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(
|
||||
fmt.Sprintf("config-file[%d]", i),
|
||||
configFile,
|
||||
fmt.Errorf("parse error: %w", err),
|
||||
),
|
||||
)
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
|
||||
if err == nil {
|
||||
config.Files = append(config.Files, *configFileParsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if parsedData.URL != nil {
|
||||
urlParsed, err := url.Parse(*parsedData.URL)
|
||||
if err != nil {
|
||||
fieldParseErrors = append(fieldParseErrors, *types.NewFieldParseError("url", *parsedData.URL, err))
|
||||
} else {
|
||||
config.URL = urlParsed
|
||||
}
|
||||
}
|
||||
|
||||
for i, proxy := range parsedData.Proxies {
|
||||
err := config.Proxies.Parse(proxy)
|
||||
if err != nil {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(fmt.Sprintf("proxy[%d]", i), proxy, err),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if len(fieldParseErrors) > 0 {
|
||||
return nil, types.NewFieldParseErrors(fieldParseErrors)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
1060
pkg/config/file_test.go
Normal file
1060
pkg/config/file_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,7 +0,0 @@
|
||||
package parser
|
||||
|
||||
import "github.com/aykhans/dodo/pkg/config"
|
||||
|
||||
type IParser interface {
|
||||
Parse() (*config.Config, error)
|
||||
}
|
@@ -63,6 +63,25 @@ func (e FieldParseErrors) Error() string {
|
||||
return errorString
|
||||
}
|
||||
|
||||
type UnmarshalError struct {
|
||||
error error
|
||||
}
|
||||
|
||||
func NewUnmarshalError(err error) UnmarshalError {
|
||||
if err == nil {
|
||||
err = ErrNoError
|
||||
}
|
||||
return UnmarshalError{err}
|
||||
}
|
||||
|
||||
func (e UnmarshalError) Error() string {
|
||||
return "Unmarshal error: " + e.error.Error()
|
||||
}
|
||||
|
||||
func (e UnmarshalError) Unwrap() error {
|
||||
return e.error
|
||||
}
|
||||
|
||||
// ======================================== CLI ========================================
|
||||
|
||||
type CLIUnexpectedArgsError struct {
|
||||
|
@@ -205,20 +205,50 @@ func TestNewRemoteConfigFileParseError(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestErrorConstants(t *testing.T) {
|
||||
t.Run("ErrNoError has correct message", func(t *testing.T) {
|
||||
expected := "no error (internal)"
|
||||
assert.Equal(t, expected, ErrNoError.Error())
|
||||
func TestUnmarshalError_Error(t *testing.T) {
|
||||
t.Run("Error returns formatted message", func(t *testing.T) {
|
||||
originalErr := errors.New("yaml parsing failed")
|
||||
err := NewUnmarshalError(originalErr)
|
||||
|
||||
expected := "Unmarshal error: yaml parsing failed"
|
||||
assert.Equal(t, expected, err.Error())
|
||||
})
|
||||
|
||||
t.Run("ErrCLINoArgs has correct message", func(t *testing.T) {
|
||||
expected := "CLI expects arguments but received none"
|
||||
assert.Equal(t, expected, ErrCLINoArgs.Error())
|
||||
t.Run("Error with nil underlying error", func(t *testing.T) {
|
||||
err := NewUnmarshalError(nil)
|
||||
|
||||
expected := "Unmarshal error: no error (internal)"
|
||||
assert.Equal(t, expected, err.Error())
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshalError_Unwrap(t *testing.T) {
|
||||
t.Run("Unwrap returns original error", func(t *testing.T) {
|
||||
originalErr := errors.New("original error")
|
||||
err := NewUnmarshalError(originalErr)
|
||||
|
||||
assert.Equal(t, originalErr, err.Unwrap())
|
||||
})
|
||||
|
||||
t.Run("ErrCLIUnexpectedArgs has correct message", func(t *testing.T) {
|
||||
expected := "CLI received unexpected arguments"
|
||||
assert.Equal(t, expected, ErrCLIUnexpectedArgs.Error())
|
||||
t.Run("Unwrap with nil error", func(t *testing.T) {
|
||||
err := NewUnmarshalError(nil)
|
||||
|
||||
assert.Equal(t, ErrNoError, err.Unwrap())
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewUnmarshalError(t *testing.T) {
|
||||
t.Run("Creates UnmarshalError with correct values", func(t *testing.T) {
|
||||
originalErr := errors.New("test error")
|
||||
err := NewUnmarshalError(originalErr)
|
||||
|
||||
assert.Equal(t, originalErr, err.error)
|
||||
})
|
||||
|
||||
t.Run("Creates UnmarshalError with ErrNoError when nil passed", func(t *testing.T) {
|
||||
err := NewUnmarshalError(nil)
|
||||
|
||||
assert.Equal(t, ErrNoError, err.error)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -242,4 +272,9 @@ func TestErrorImplementsErrorInterface(t *testing.T) {
|
||||
var err error = NewRemoteConfigFileParseError(errors.New("test"))
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("UnmarshalError implements error interface", func(t *testing.T) {
|
||||
var err error = NewUnmarshalError(errors.New("test"))
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
Reference in New Issue
Block a user