diff --git a/configio/converters.go b/configio/converters.go new file mode 100644 index 0000000..847dbc2 --- /dev/null +++ b/configio/converters.go @@ -0,0 +1,133 @@ +package configio + +import ( + "bytes" + "errors" + "fmt" + "io" + "net/http" + "time" + + "github.com/benchttp/engine/benchttp" +) + +type ( + converter func(representation, *benchttp.Runner) error + requestConverter func(representation, *http.Request) error +) + +var fieldConverters = []converter{ + func(repr representation, dst *benchttp.Runner) error { + req := dst.Request + if req == nil { + req = &http.Request{} + } + for _, p := range requestConverters { + if err := p(repr, req); err != nil { + return err + } + } + dst.Request = req + return nil + }, + func(repr representation, dst *benchttp.Runner) error { + for _, p := range runnerParsers { + if err := p(repr, dst); err != nil { + return err + } + } + return nil + }, +} + +var requestParser = func(repr representation, dst *benchttp.Runner) error { + req := &http.Request{} + for _, fieldParser := range requestConverters { + if err := fieldParser(repr, req); err != nil { + return err + } + } + dst.Request = req + return nil +} + +var requestConverters = []requestConverter{ + func(repr representation, dst *http.Request) error { + return setString(repr.Request.Method, &dst.Method) + }, + func(repr representation, dst *http.Request) error { + if rawURL := repr.Request.URL; rawURL != nil { + parsedURL, err := parseAndBuildURL(*rawURL, repr.Request.QueryParams) + if err != nil { + return fmt.Errorf(`configio: invalid url: %q`, *rawURL) + } + dst.URL = parsedURL + } + return nil + }, + func(repr representation, dst *http.Request) error { + if header := repr.Request.Header; len(header) != 0 { + httpHeader := http.Header{} + for key, val := range header { + httpHeader[key] = val + } + dst.Header = httpHeader + } + return nil + }, + func(repr representation, dst *http.Request) error { + if body := repr.Request.Body; body != nil { + switch body.Type { + case "raw": + dst.Body = io.NopCloser(bytes.NewReader([]byte(body.Content))) + default: + return errors.New(`configio: request.body.type: only "raw" accepted`) + } + } + return nil + }, +} + +var runnerParsers = map[string]converter{ + "requests": func(repr representation, dst *benchttp.Runner) error { + return setInt(repr.Runner.Requests, &dst.Requests) + }, + "concurrency": func(repr representation, dst *benchttp.Runner) error { + return setInt(repr.Runner.Concurrency, &dst.Concurrency) + }, + "interval": func(repr representation, dst *benchttp.Runner) error { + return setOptionalDuration(repr.Runner.Interval, &dst.Interval) + }, + "requestTimeout": func(repr representation, dst *benchttp.Runner) error { + return setOptionalDuration(repr.Runner.RequestTimeout, &dst.RequestTimeout) + }, + "globalTimeout": func(repr representation, dst *benchttp.Runner) error { + return setOptionalDuration(repr.Runner.GlobalTimeout, &dst.GlobalTimeout) + }, +} + +func setInt(src, dst *int) error { + if src != nil { + *dst = *src + } + return nil +} + +func setString(src, dst *string) error { + if src != nil { + *dst = *src + } + return nil +} + +func setOptionalDuration(src *string, dst *time.Duration) error { + if src == nil { + return nil + } + parsed, err := parseOptionalDuration(*src) + if err != nil { + return err + } + *dst = parsed + return nil +} diff --git a/configio/representation.go b/configio/representation.go index bf851a1..76a0fc6 100644 --- a/configio/representation.go +++ b/configio/representation.go @@ -1,11 +1,7 @@ package configio import ( - "bytes" - "errors" "fmt" - "io" - "net/http" "net/url" "strconv" "time" @@ -57,86 +53,13 @@ func (repr representation) validate() error { // and stores any non-nil field value into the corresponding field // of dst. func (repr representation) parseAndMutate(dst *benchttp.Runner) error { - if err := repr.parseRequestInto(dst); err != nil { - return err - } - if err := repr.parseRunnerInto(dst); err != nil { - return err - } - return repr.parseTestsInto(dst) -} - -func (repr representation) parseRequestInto(dst *benchttp.Runner) error { - if dst.Request == nil { - dst.Request = &http.Request{} - } - - if method := repr.Request.Method; method != nil { - dst.Request.Method = *method - } - - if rawURL := repr.Request.URL; rawURL != nil { - parsedURL, err := parseAndBuildURL(*rawURL, repr.Request.QueryParams) - if err != nil { - return fmt.Errorf(`configio: invalid url: %q`, *rawURL) - } - dst.Request.URL = parsedURL - } - - if header := repr.Request.Header; len(header) != 0 { - httpHeader := http.Header{} - for key, val := range header { - httpHeader[key] = val - } - dst.Request.Header = httpHeader - } - - if body := repr.Request.Body; body != nil { - switch body.Type { - case "raw": - dst.Request.Body = io.NopCloser(bytes.NewReader([]byte(body.Content))) - default: - return errors.New(`configio: request.body.type: only "raw" accepted`) - } - } - - return nil -} - -func (repr representation) parseRunnerInto(dst *benchttp.Runner) error { - if requests := repr.Runner.Requests; requests != nil { - dst.Requests = *requests - } - - if concurrency := repr.Runner.Concurrency; concurrency != nil { - dst.Concurrency = *concurrency - } - - if interval := repr.Runner.Interval; interval != nil { - parsedInterval, err := parseOptionalDuration(*interval) - if err != nil { - return err - } - dst.Interval = parsedInterval - } - - if requestTimeout := repr.Runner.RequestTimeout; requestTimeout != nil { - parsedTimeout, err := parseOptionalDuration(*requestTimeout) - if err != nil { - return err - } - dst.RequestTimeout = parsedTimeout - } - - if globalTimeout := repr.Runner.GlobalTimeout; globalTimeout != nil { - parsedGlobalTimeout, err := parseOptionalDuration(*globalTimeout) - if err != nil { + for _, converter := range fieldConverters { + if err := converter(repr, dst); err != nil { return err } - dst.GlobalTimeout = parsedGlobalTimeout } - - return nil + // TODO: use converter for tests + return repr.parseTestsInto(dst) } func (repr representation) parseTestsInto(dst *benchttp.Runner) error {