mirror of
https://github.com/aykhans/dodo.git
synced 2025-09-08 12:00:47 +00:00
185 lines
4.8 KiB
Go
185 lines
4.8 KiB
Go
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
|
|
}
|