Files
dodo/pkg/config/file.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
}