From f248c2af9696dd856b96c1e6e5b64d9b882dc915 Mon Sep 17 00:00:00 2001 From: Aykhan Shahsuvarov Date: Fri, 30 May 2025 10:40:20 +0400 Subject: [PATCH 1/7] Value generator initial commit --- go.mod | 1 + go.sum | 2 + requests/request.go | 116 ++++++++++++++++++++++++++++---------------- requests/run.go | 2 +- utils/slice.go | 10 ++-- 5 files changed, 81 insertions(+), 50 deletions(-) diff --git a/go.mod b/go.mod index 86b726e..29b3897 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.24.2 require ( github.com/jedib0t/go-pretty/v6 v6.6.7 github.com/valyala/fasthttp v1.62.0 + github.com/brianvoe/gofakeit/v7 v7.2.1 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 85194b4..01ce699 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/brianvoe/gofakeit/v7 v7.2.1 h1:AGojgaaCdgq4Adzrd2uWdbGNDyX6MWNhHdQBraNfOHI= +github.com/brianvoe/gofakeit/v7 v7.2.1/go.mod h1:QXuPeBw164PJCzCUZVmgpgHJ3Llj49jSLVkKPMtxtxA= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/jedib0t/go-pretty/v6 v6.6.7 h1:m+LbHpm0aIAPLzLbMfn8dc3Ht8MW7lsSO4MPItz/Uuo= diff --git a/requests/request.go b/requests/request.go index 8624655..c255a26 100644 --- a/requests/request.go +++ b/requests/request.go @@ -1,14 +1,18 @@ package requests import ( + "bytes" "context" "math/rand" "net/url" + "strings" + "text/template" "time" "github.com/aykhans/dodo/config" "github.com/aykhans/dodo/types" "github.com/aykhans/dodo/utils" + "github.com/brianvoe/gofakeit/v7" "github.com/valyala/fasthttp" ) @@ -21,6 +25,11 @@ type Request struct { getRequest RequestGeneratorFunc } +type keyValueGenerator struct { + key func() string + value func() string +} + // Send sends the HTTP request using the fasthttp client with a specified timeout. // It returns the HTTP response or an error if the request fails or times out. func (r *Request) Send(ctx context.Context, timeout time.Duration) (*fasthttp.Response, error) { @@ -101,17 +110,10 @@ func getRequestGeneratorFunc( bodies []string, localRand *rand.Rand, ) RequestGeneratorFunc { - bodiesLen := len(bodies) - getBody := func() string { return "" } - if bodiesLen == 1 { - getBody = func() string { return bodies[0] } - } else if bodiesLen > 1 { - getBody = utils.RandomValueCycle(bodies, localRand) - } - getParams := getKeyValueGeneratorFunc(params, localRand) getHeaders := getKeyValueGeneratorFunc(headers, localRand) getCookies := getKeyValueGeneratorFunc(cookies, localRand) + getBody := getValueFunc(bodies, newFuncMap(localRand), localRand) return func() *fasthttp.Request { return newFasthttpRequest( @@ -199,45 +201,75 @@ func getKeyValueGeneratorFunc[ keyValueSlice []types.KeyValue[string, []string], localRand *rand.Rand, ) func() T { - getKeyValueSlice := []map[string]func() string{} - isRandom := false + keyValueGenerators := make([]keyValueGenerator, len(keyValueSlice)) - for _, kv := range keyValueSlice { - if valuesLen := len(kv.Value); valuesLen > 1 { - isRandom = true + funcMap := newFuncMap(localRand) + + for i, kv := range keyValueSlice { + keyValueGenerators[i] = keyValueGenerator{ + key: getKeyFunc(kv.Key, funcMap), + value: getValueFunc(kv.Value, funcMap, localRand), } - - getKeyValueSlice = append( - getKeyValueSlice, - map[string]func() string{ - kv.Key: utils.RandomValueCycle(kv.Value, localRand), - }, - ) } - if isRandom { - return func() T { - keyValues := make(T, len(getKeyValueSlice)) - for i, keyValue := range getKeyValueSlice { - for key, value := range keyValue { - keyValues[i] = types.KeyValue[string, string]{ - Key: key, - Value: value(), - } - } - } - return keyValues - } - } else { - keyValues := make(T, len(getKeyValueSlice)) - for i, keyValue := range getKeyValueSlice { - for key, value := range keyValue { - keyValues[i] = types.KeyValue[string, string]{ - Key: key, - Value: value(), - } + return func() T { + keyValues := make(T, len(keyValueGenerators)) + for i, keyValue := range keyValueGenerators { + keyValues[i] = types.KeyValue[string, string]{ + Key: keyValue.key(), + Value: keyValue.value(), } } - return func() T { return keyValues } + return keyValues + } +} + +func getKeyFunc(key string, funcMap template.FuncMap) func() string { + t, err := template.New("default").Funcs(funcMap).Parse(key) + if err != nil { + panic(err) + } + + return func() string { + var buf bytes.Buffer + _ = t.Execute(&buf, nil) + return buf.String() + } +} + +func getValueFunc( + values []string, + funcMap template.FuncMap, + localRand *rand.Rand, +) func() string { + templates := make([]*template.Template, len(values)) + + for i, value := range values { + t, err := template.New("default").Funcs(funcMap).Parse(value) + if err != nil { + panic(err) + } + templates[i] = t + } + + randomTemplateFunc := utils.RandomValueCycle(templates, localRand) + + return func() string { + if tmpl := randomTemplateFunc(); tmpl == nil { + return "" + } else { + var buf bytes.Buffer + _ = tmpl.Execute(&buf, nil) + return buf.String() + } + } +} + +func newFuncMap(localRand *rand.Rand) template.FuncMap { + localFaker := gofakeit.NewFaker(localRand, false) + + return template.FuncMap{ + "upper": strings.ToUpper, + "fakeit_Name": localFaker.Name, } } diff --git a/requests/run.go b/requests/run.go index 3441533..2095a2b 100644 --- a/requests/run.go +++ b/requests/run.go @@ -72,7 +72,7 @@ func releaseDodos( wg.Add(int(dodosCount)) streamWG.Add(1) - streamCtx, streamCtxCancel := context.WithCancel(context.Background()) + streamCtx, streamCtxCancel := context.WithCancel(ctx) go streamProgress(streamCtx, &streamWG, requestConfig.RequestCount, "Dodos Working🔥", increase) diff --git a/utils/slice.go b/utils/slice.go index b3d5f4e..0edeac9 100644 --- a/utils/slice.go +++ b/utils/slice.go @@ -19,19 +19,15 @@ func Flatten[T any](nested [][]*T) []*T { // reset and start cycling through the values in a random order again. // The returned function isn't thread-safe and should be used in a single-threaded context. func RandomValueCycle[T any](values []T, localRand *rand.Rand) func() T { - var ( - valuesLen = len(values) - currentIndex = localRand.Intn(valuesLen) - stopIndex = currentIndex - ) - - switch valuesLen { + switch valuesLen := len(values); valuesLen { case 0: var zero T return func() T { return zero } case 1: return func() T { return values[0] } default: + currentIndex := localRand.Intn(valuesLen) + stopIndex := currentIndex return func() T { value := values[currentIndex] currentIndex++ From 79668e4ecee9e167ee472ab6e5b4acba4fd3514e Mon Sep 17 00:00:00 2001 From: Aykhan Shahsuvarov Date: Fri, 30 May 2025 21:41:38 +0400 Subject: [PATCH 2/7] =?UTF-8?q?=E2=9C=A8=20Add=20value=20generators?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- README.md | 51 ++++++ Taskfile.yaml | 2 +- config/config.go | 89 ++++++++++ go.mod | 2 +- requests/request.go | 42 +++-- utils/slice.go | 13 +- utils/templates.go | 386 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 561 insertions(+), 26 deletions(-) create mode 100644 utils/templates.go diff --git a/Dockerfile b/Dockerfile index bd26da1..28b8c13 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ COPY go.mod go.sum ./ RUN go mod download COPY . . -RUN go build -ldflags "-s -w" -o dodo +RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o dodo FROM gcr.io/distroless/static-debian12:latest diff --git a/README.md b/README.md index 6be4185..954f1df 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ - [2.2 YAML/YML Example](#22-yamlyml-example) - [3. CLI & Config File Combination](#3-cli--config-file-combination) - [Config Parameters Reference](#config-parameters-reference) +- [Template Functions](#template-functions) ## Installation @@ -264,3 +265,53 @@ If `Headers`, `Params`, `Cookies`, `Body`, or `Proxy` fields have multiple value | Body | body | -body | -b | String OR [String] | Request body or list of request bodies | - | | Proxy | proxies | -proxy | -x | String OR [String] | Proxy URL or list of proxy URLs | - | | Skip Verify | skip_verify | -skip-verify | | Boolean | Skip SSL/TLS certificate verification | false | + +## Template Functions + +Dodo supports template functions in `Headers`, `Params`, `Cookies`, and `Body` fields. These functions allow you to generate dynamic values for each request. + +You can use Go template syntax to include dynamic values in your requests. Here's how to use template functions: + +In CLI config: + +```sh +dodo -u https://example.com -r 1 \ + -header "User-Agent:{{ fakeit_UserAgent }}" \ # e.g. "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)" + -param "username={{ fakeit_Username }}" \ # e.g. "username=John Bob" + -cookie "token={{ fakeit_Password true true true true true 10 }}" \ # e.g. token=1234567890abcdef1234567890abcdef + -body '{"email":"{{ fakeit_Email }}", "password":"{{ fakeit_Password true true true true true 10 }}"}' # e.g. {"email":"john.doe@example.com", "password":"12rw4d-78d"} +``` + +In YAML/YML config: + +```yaml +headers: + - User-Agent: "{{ fakeit_UserAgent }}" # e.g. "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)" + - "Random-Header-{{fakeit_FirstName}}": "static_value" # e.g. "Random-Header-John: static_value" + +cookies: + - token: "Bearer {{ fakeit_UUID }}" # e.g. "token=Bearer 1234567890abcdef1234567890abcdef" + +params: + - id: "{{ fakeit_Uint }}" # e.g. "id=1234567890" + - username: "{{ fakeit_Username }}" # e.g. "username=John Doe" + +body: + - '{ "username": "{{ fakeit_Username }}", "password": "{{ fakeit_Password }}" }' # e.g. { "username": "john.doe", "password": "password123" } + - '{ "email": "{{ fakeit_Email }}", "phone": "{{ fakeit_Phone }}" }' # e.g. { "email": "john.doe@example.com", "phone": "+1234567890" } +``` + +In JSON config: + +```jsonc +{ + "headers": [ + { "User-Agent": "{{ fakeit_UserAgent }}" }, // e.g. "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)" + ], + "body": [ + "{ \"username\": \"{{ fakeit_Username }}\", \"password\": \"{{ fakeit_Password }}\" }", // e.g. { "username": "john.doe", "password": "password123" } + ], +} +``` + +For the full list of template functions over 200 functions, refer to the `newFuncMap` function in `requests/request.go`. diff --git a/Taskfile.yaml b/Taskfile.yaml index e8e2ff8..c1730c4 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -32,7 +32,7 @@ tasks: lint: golangci-lint run - build: go build -ldflags "-s -w" -o "dodo" + build: CGO_ENABLED=0 go build -ldflags "-s -w" -o "dodo" build-all: silent: true diff --git a/config/config.go b/config/config.go index 8b9414f..322d160 100644 --- a/config/config.go +++ b/config/config.go @@ -1,12 +1,15 @@ package config import ( + "bytes" "errors" "fmt" + "math/rand" "net/url" "os" "slices" "strings" + "text/template" "time" "github.com/aykhans/dodo/types" @@ -201,6 +204,92 @@ func (config *Config) Validate() []error { } } + funcMap := utils.NewFuncMap(rand.New(rand.NewSource(time.Now().UnixNano()))) + + for _, header := range config.Headers { + t, err := template.New("default").Funcs(funcMap).Parse(header.Key) + if err != nil { + errs = append(errs, fmt.Errorf("header key (%s) parse error: %v", header.Key, err)) + } else { + var buf bytes.Buffer + if err = t.Execute(&buf, nil); err != nil { + errs = append(errs, fmt.Errorf("header key (%s) parse error: %v", header.Key, err)) + } + } + + for _, value := range header.Value { + t, err := template.New("default").Funcs(funcMap).Parse(value) + if err != nil { + errs = append(errs, fmt.Errorf("header value (%s) parse error: %v", value, err)) + } else { + var buf bytes.Buffer + if err = t.Execute(&buf, nil); err != nil { + errs = append(errs, fmt.Errorf("header value (%s) parse error: %v", value, err)) + } + } + } + } + + for _, cookie := range config.Cookies { + t, err := template.New("default").Funcs(funcMap).Parse(cookie.Key) + if err != nil { + errs = append(errs, fmt.Errorf("cookie key (%s) parse error: %v", cookie.Key, err)) + } else { + var buf bytes.Buffer + if err = t.Execute(&buf, nil); err != nil { + errs = append(errs, fmt.Errorf("cookie key (%s) parse error: %v", cookie.Key, err)) + } + } + + for _, value := range cookie.Value { + t, err := template.New("default").Funcs(funcMap).Parse(value) + if err != nil { + errs = append(errs, fmt.Errorf("cookie value (%s) parse error: %v", value, err)) + } else { + var buf bytes.Buffer + if err = t.Execute(&buf, nil); err != nil { + errs = append(errs, fmt.Errorf("cookie value (%s) parse error: %v", value, err)) + } + } + } + } + + for _, param := range config.Params { + t, err := template.New("default").Funcs(funcMap).Parse(param.Key) + if err != nil { + errs = append(errs, fmt.Errorf("param key (%s) parse error: %v", param.Key, err)) + } else { + var buf bytes.Buffer + if err = t.Execute(&buf, nil); err != nil { + errs = append(errs, fmt.Errorf("param key (%s) parse error: %v", param.Key, err)) + } + } + + for _, value := range param.Value { + t, err := template.New("default").Funcs(funcMap).Parse(value) + if err != nil { + errs = append(errs, fmt.Errorf("param value (%s) parse error: %v", value, err)) + } else { + var buf bytes.Buffer + if err = t.Execute(&buf, nil); err != nil { + errs = append(errs, fmt.Errorf("param value (%s) parse error: %v", value, err)) + } + } + } + } + + for _, body := range config.Body { + t, err := template.New("default").Funcs(funcMap).Parse(body) + if err != nil { + errs = append(errs, fmt.Errorf("body (%s) parse error: %v", body, err)) + } else { + var buf bytes.Buffer + if err = t.Execute(&buf, nil); err != nil { + errs = append(errs, fmt.Errorf("body (%s) parse error: %v", body, err)) + } + } + } + return errs } diff --git a/go.mod b/go.mod index 29b3897..d972a38 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,9 @@ module github.com/aykhans/dodo go 1.24.2 require ( + github.com/brianvoe/gofakeit/v7 v7.2.1 github.com/jedib0t/go-pretty/v6 v6.6.7 github.com/valyala/fasthttp v1.62.0 - github.com/brianvoe/gofakeit/v7 v7.2.1 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/requests/request.go b/requests/request.go index c255a26..0e32e49 100644 --- a/requests/request.go +++ b/requests/request.go @@ -5,14 +5,12 @@ import ( "context" "math/rand" "net/url" - "strings" "text/template" "time" "github.com/aykhans/dodo/config" "github.com/aykhans/dodo/types" "github.com/aykhans/dodo/utils" - "github.com/brianvoe/gofakeit/v7" "github.com/valyala/fasthttp" ) @@ -113,7 +111,7 @@ func getRequestGeneratorFunc( getParams := getKeyValueGeneratorFunc(params, localRand) getHeaders := getKeyValueGeneratorFunc(headers, localRand) getCookies := getKeyValueGeneratorFunc(cookies, localRand) - getBody := getValueFunc(bodies, newFuncMap(localRand), localRand) + getBody := getValueFunc(bodies, utils.NewFuncMap(localRand), localRand) return func() *fasthttp.Request { return newFasthttpRequest( @@ -203,7 +201,7 @@ func getKeyValueGeneratorFunc[ ) func() T { keyValueGenerators := make([]keyValueGenerator, len(keyValueSlice)) - funcMap := newFuncMap(localRand) + funcMap := utils.NewFuncMap(localRand) for i, kv := range keyValueSlice { keyValueGenerators[i] = keyValueGenerator{ @@ -224,10 +222,18 @@ func getKeyValueGeneratorFunc[ } } +// getKeyFunc creates a function that processes a key string through Go's template engine. +// It takes a key string and a template.FuncMap containing the available template functions. +// +// The returned function, when called, will execute the template with the given key and return +// the processed string result. If template parsing fails, the returned function will always +// return an empty string. +// +// This enables dynamic generation of keys that can include template directives and functions. func getKeyFunc(key string, funcMap template.FuncMap) func() string { t, err := template.New("default").Funcs(funcMap).Parse(key) if err != nil { - panic(err) + return func() string { return "" } } return func() string { @@ -237,6 +243,21 @@ func getKeyFunc(key string, funcMap template.FuncMap) func() string { } } +// getValueFunc creates a function that randomly selects and processes a value from a slice of strings +// through Go's template engine. +// +// Parameters: +// - values: A slice of string templates that can contain template directives +// - funcMap: A template.FuncMap containing all available template functions +// - localRand: A random number generator for consistent randomization +// +// The returned function, when called, will: +// 1. Select a random template from the values slice +// 2. Execute the selected template +// 3. Return the processed string result +// +// If a selected template is nil (due to earlier parsing failure), the function will return an empty string. +// This enables dynamic generation of values with randomized selection from multiple templates. func getValueFunc( values []string, funcMap template.FuncMap, @@ -247,7 +268,7 @@ func getValueFunc( for i, value := range values { t, err := template.New("default").Funcs(funcMap).Parse(value) if err != nil { - panic(err) + templates[i] = nil } templates[i] = t } @@ -264,12 +285,3 @@ func getValueFunc( } } } - -func newFuncMap(localRand *rand.Rand) template.FuncMap { - localFaker := gofakeit.NewFaker(localRand, false) - - return template.FuncMap{ - "upper": strings.ToUpper, - "fakeit_Name": localFaker.Name, - } -} diff --git a/utils/slice.go b/utils/slice.go index 0edeac9..1f56656 100644 --- a/utils/slice.go +++ b/utils/slice.go @@ -10,14 +10,11 @@ func Flatten[T any](nested [][]*T) []*T { return flattened } -// RandomValueCycle returns a function that cycles through the provided slice of values -// in a random order. Each call to the returned function will yield a value from the slice. -// The order of values is determined by the provided random number generator. -// -// The returned function will cycle through the values in a random order until all values -// have been returned at least once. After all values have been returned, the function will -// reset and start cycling through the values in a random order again. -// The returned function isn't thread-safe and should be used in a single-threaded context. +// RandomValueCycle returns a function that cycles through the provided values in a pseudo-random order. +// Each value in the input slice will be returned before any value is repeated. +// If the input slice is empty, the returned function will always return the zero value of type T. +// If the input slice contains only one element, that element is always returned. +// This function is not thread-safe and should not be called concurrently. func RandomValueCycle[T any](values []T, localRand *rand.Rand) func() T { switch valuesLen := len(values); valuesLen { case 0: diff --git a/utils/templates.go b/utils/templates.go new file mode 100644 index 0000000..d9fe610 --- /dev/null +++ b/utils/templates.go @@ -0,0 +1,386 @@ +package utils + +import ( + "math/rand" + "strings" + "text/template" + "time" + + "github.com/brianvoe/gofakeit/v7" +) + +// NewFuncMap creates a template.FuncMap populated with string manipulation functions +// and data generation functions from gofakeit. +// +// It takes a random number generator that is used to initialize a localized faker +// instance, ensuring that random data generation is deterministic within a request context. +// +// All functions are prefixed to avoid naming conflicts: +// - String functions: "strings_*" +// - Data generation functions: "fakeit_*" +func NewFuncMap(localRand *rand.Rand) template.FuncMap { + localFaker := gofakeit.NewFaker(localRand, false) + + return template.FuncMap{ + // Strings + "strings_ToUpper": strings.ToUpper, + "strings_ToLower": strings.ToLower, + "strings_RemoveSpaces": func(s string) string { return strings.ReplaceAll(s, " ", "") }, + "strings_Replace": strings.Replace, + "strings_ToDate": func(dateString string) time.Time { + date, err := time.Parse("2006-01-02", dateString) + if err != nil { + return time.Now() + } + return date + }, + + // FakeIt / Product + "fakeit_ProductName": localFaker.ProductName, + "fakeit_ProductDescription": localFaker.ProductDescription, + "fakeit_ProductCategory": localFaker.ProductCategory, + "fakeit_ProductFeature": localFaker.ProductFeature, + "fakeit_ProductMaterial": localFaker.ProductMaterial, + "fakeit_ProductUPC": localFaker.ProductUPC, + "fakeit_ProductAudience": localFaker.ProductAudience, + "fakeit_ProductDimension": localFaker.ProductDimension, + "fakeit_ProductUseCase": localFaker.ProductUseCase, + "fakeit_ProductBenefit": localFaker.ProductBenefit, + "fakeit_ProductSuffix": localFaker.ProductSuffix, + + // FakeIt / Person + "fakeit_Name": localFaker.Name, + "fakeit_NamePrefix": localFaker.NamePrefix, + "fakeit_NameSuffix": localFaker.NameSuffix, + "fakeit_FirstName": localFaker.FirstName, + "fakeit_MiddleName": localFaker.MiddleName, + "fakeit_LastName": localFaker.LastName, + "fakeit_Gender": localFaker.Gender, + "fakeit_SSN": localFaker.SSN, + "fakeit_Hobby": localFaker.Hobby, + "fakeit_Email": localFaker.Email, + "fakeit_Phone": localFaker.Phone, + "fakeit_PhoneFormatted": localFaker.PhoneFormatted, + + // FakeIt / Auth + "fakeit_Username": localFaker.Username, + "fakeit_Password": localFaker.Password, + + // FakeIt / Address + "fakeit_City": localFaker.City, + "fakeit_Country": localFaker.Country, + "fakeit_CountryAbr": localFaker.CountryAbr, + "fakeit_State": localFaker.State, + "fakeit_StateAbr": localFaker.StateAbr, + "fakeit_Street": localFaker.Street, + "fakeit_StreetName": localFaker.StreetName, + "fakeit_StreetNumber": localFaker.StreetNumber, + "fakeit_StreetPrefix": localFaker.StreetPrefix, + "fakeit_StreetSuffix": localFaker.StreetSuffix, + "fakeit_Zip": localFaker.Zip, + "fakeit_Latitude": localFaker.Latitude, + "fakeit_LatitudeInRange": func(min, max float64) float64 { + value, err := localFaker.LatitudeInRange(min, max) + if err != nil { + var zero float64 + return zero + } + return value + }, + "fakeit_Longitude": localFaker.Longitude, + "fakeit_LongitudeInRange": func(min, max float64) float64 { + value, err := localFaker.LongitudeInRange(min, max) + if err != nil { + var zero float64 + return zero + } + return value + }, + + // FakeIt / Game + "fakeit_Gamertag": localFaker.Gamertag, + + // FakeIt / Beer + "fakeit_BeerAlcohol": localFaker.BeerAlcohol, + "fakeit_BeerBlg": localFaker.BeerBlg, + "fakeit_BeerHop": localFaker.BeerHop, + "fakeit_BeerIbu": localFaker.BeerIbu, + "fakeit_BeerMalt": localFaker.BeerMalt, + "fakeit_BeerName": localFaker.BeerName, + "fakeit_BeerStyle": localFaker.BeerStyle, + "fakeit_BeerYeast": localFaker.BeerYeast, + + // FakeIt / Car + "fakeit_CarMaker": localFaker.CarMaker, + "fakeit_CarModel": localFaker.CarModel, + "fakeit_CarType": localFaker.CarType, + "fakeit_CarFuelType": localFaker.CarFuelType, + "fakeit_CarTransmissionType": localFaker.CarTransmissionType, + + // FakeIt / Words + "fakeit_Noun": localFaker.Noun, + "fakeit_NounCommon": localFaker.NounCommon, + "fakeit_NounConcrete": localFaker.NounConcrete, + "fakeit_NounAbstract": localFaker.NounAbstract, + "fakeit_NounCollectivePeople": localFaker.NounCollectivePeople, + "fakeit_NounCollectiveAnimal": localFaker.NounCollectiveAnimal, + "fakeit_NounCollectiveThing": localFaker.NounCollectiveThing, + "fakeit_NounCountable": localFaker.NounCountable, + "fakeit_NounUncountable": localFaker.NounUncountable, + "fakeit_Verb": localFaker.Verb, + "fakeit_VerbAction": localFaker.VerbAction, + "fakeit_VerbLinking": localFaker.VerbLinking, + "fakeit_VerbHelping": localFaker.VerbHelping, + "fakeit_Adverb": localFaker.Adverb, + "fakeit_AdverbManner": localFaker.AdverbManner, + "fakeit_AdverbDegree": localFaker.AdverbDegree, + "fakeit_AdverbPlace": localFaker.AdverbPlace, + "fakeit_AdverbTimeDefinite": localFaker.AdverbTimeDefinite, + "fakeit_AdverbTimeIndefinite": localFaker.AdverbTimeIndefinite, + "fakeit_AdverbFrequencyDefinite": localFaker.AdverbFrequencyDefinite, + "fakeit_AdverbFrequencyIndefinite": localFaker.AdverbFrequencyIndefinite, + "fakeit_Preposition": localFaker.Preposition, + "fakeit_PrepositionSimple": localFaker.PrepositionSimple, + "fakeit_PrepositionDouble": localFaker.PrepositionDouble, + "fakeit_PrepositionCompound": localFaker.PrepositionCompound, + "fakeit_Adjective": localFaker.Adjective, + "fakeit_AdjectiveDescriptive": localFaker.AdjectiveDescriptive, + "fakeit_AdjectiveQuantitative": localFaker.AdjectiveQuantitative, + "fakeit_AdjectiveProper": localFaker.AdjectiveProper, + "fakeit_AdjectiveDemonstrative": localFaker.AdjectiveDemonstrative, + "fakeit_AdjectivePossessive": localFaker.AdjectivePossessive, + "fakeit_AdjectiveInterrogative": localFaker.AdjectiveInterrogative, + "fakeit_AdjectiveIndefinite": localFaker.AdjectiveIndefinite, + "fakeit_Pronoun": localFaker.Pronoun, + "fakeit_PronounPersonal": localFaker.PronounPersonal, + "fakeit_PronounObject": localFaker.PronounObject, + "fakeit_PronounPossessive": localFaker.PronounPossessive, + "fakeit_PronounReflective": localFaker.PronounReflective, + "fakeit_PronounDemonstrative": localFaker.PronounDemonstrative, + "fakeit_PronounInterrogative": localFaker.PronounInterrogative, + "fakeit_PronounRelative": localFaker.PronounRelative, + "fakeit_Connective": localFaker.Connective, + "fakeit_ConnectiveTime": localFaker.ConnectiveTime, + "fakeit_ConnectiveComparative": localFaker.ConnectiveComparative, + "fakeit_ConnectiveComplaint": localFaker.ConnectiveComplaint, + "fakeit_ConnectiveListing": localFaker.ConnectiveListing, + "fakeit_ConnectiveCasual": localFaker.ConnectiveCasual, + "fakeit_ConnectiveExamplify": localFaker.ConnectiveExamplify, + "fakeit_Word": localFaker.Word, + "fakeit_Sentence": localFaker.Sentence, + "fakeit_Paragraph": localFaker.Paragraph, + "fakeit_LoremIpsumWord": localFaker.LoremIpsumWord, + "fakeit_LoremIpsumSentence": localFaker.LoremIpsumSentence, + "fakeit_LoremIpsumParagraph": localFaker.LoremIpsumParagraph, + "fakeit_Question": localFaker.Question, + "fakeit_Quote": localFaker.Quote, + "fakeit_Phrase": localFaker.Phrase, + + // FakeIt / Foods + "fakeit_Fruit": localFaker.Fruit, + "fakeit_Vegetable": localFaker.Vegetable, + "fakeit_Breakfast": localFaker.Breakfast, + "fakeit_Lunch": localFaker.Lunch, + "fakeit_Dinner": localFaker.Dinner, + "fakeit_Snack": localFaker.Snack, + "fakeit_Dessert": localFaker.Dessert, + + // FakeIt / Misc + "fakeit_Bool": localFaker.Bool, + "fakeit_UUID": localFaker.UUID, + "fakeit_FlipACoin": localFaker.FlipACoin, + + // FakeIt / Colors + "fakeit_Color": localFaker.Color, + "fakeit_HexColor": localFaker.HexColor, + "fakeit_RGBColor": localFaker.RGBColor, + "fakeit_SafeColor": localFaker.SafeColor, + "fakeit_NiceColors": localFaker.NiceColors, + + // FakeIt / Internet + "fakeit_URL": localFaker.URL, + "fakeit_DomainName": localFaker.DomainName, + "fakeit_DomainSuffix": localFaker.DomainSuffix, + "fakeit_IPv4Address": localFaker.IPv4Address, + "fakeit_IPv6Address": localFaker.IPv6Address, + "fakeit_MacAddress": localFaker.MacAddress, + "fakeit_HTTPStatusCode": localFaker.HTTPStatusCode, + "fakeit_HTTPStatusCodeSimple": localFaker.HTTPStatusCodeSimple, + "fakeit_LogLevel": localFaker.LogLevel, + "fakeit_HTTPMethod": localFaker.HTTPMethod, + "fakeit_HTTPVersion": localFaker.HTTPVersion, + "fakeit_UserAgent": localFaker.UserAgent, + "fakeit_ChromeUserAgent": localFaker.ChromeUserAgent, + "fakeit_FirefoxUserAgent": localFaker.FirefoxUserAgent, + "fakeit_OperaUserAgent": localFaker.OperaUserAgent, + "fakeit_SafariUserAgent": localFaker.SafariUserAgent, + + // FakeIt / HTML + "fakeit_InputName": localFaker.InputName, + + // FakeIt / Date/Time + "fakeit_Date": localFaker.Date, + "fakeit_PastDate": localFaker.PastDate, + "fakeit_FutureDate": localFaker.FutureDate, + "fakeit_DateRange": localFaker.DateRange, + "fakeit_NanoSecond": localFaker.NanoSecond, + "fakeit_Second": localFaker.Second, + "fakeit_Minute": localFaker.Minute, + "fakeit_Hour": localFaker.Hour, + "fakeit_Month": localFaker.Month, + "fakeit_MonthString": localFaker.MonthString, + "fakeit_Day": localFaker.Day, + "fakeit_WeekDay": localFaker.WeekDay, + "fakeit_Year": localFaker.Year, + "fakeit_TimeZone": localFaker.TimeZone, + "fakeit_TimeZoneAbv": localFaker.TimeZoneAbv, + "fakeit_TimeZoneFull": localFaker.TimeZoneFull, + "fakeit_TimeZoneOffset": localFaker.TimeZoneOffset, + "fakeit_TimeZoneRegion": localFaker.TimeZoneRegion, + + // FakeIt / Payment + "fakeit_Price": localFaker.Price, + "fakeit_CreditCardCvv": localFaker.CreditCardCvv, + "fakeit_CreditCardExp": localFaker.CreditCardExp, + "fakeit_CreditCardNumber": localFaker.CreditCardNumber, + "fakeit_CreditCardType": localFaker.CreditCardType, + "fakeit_CurrencyLong": localFaker.CurrencyLong, + "fakeit_CurrencyShort": localFaker.CurrencyShort, + "fakeit_AchRouting": localFaker.AchRouting, + "fakeit_AchAccount": localFaker.AchAccount, + "fakeit_BitcoinAddress": localFaker.BitcoinAddress, + "fakeit_BitcoinPrivateKey": localFaker.BitcoinPrivateKey, + + // FakeIt / Finance + "fakeit_Cusip": localFaker.Cusip, + "fakeit_Isin": localFaker.Isin, + + // FakeIt / Company + "fakeit_BS": localFaker.BS, + "fakeit_Blurb": localFaker.Blurb, + "fakeit_BuzzWord": localFaker.BuzzWord, + "fakeit_Company": localFaker.Company, + "fakeit_CompanySuffix": localFaker.CompanySuffix, + "fakeit_JobDescriptor": localFaker.JobDescriptor, + "fakeit_JobLevel": localFaker.JobLevel, + "fakeit_JobTitle": localFaker.JobTitle, + "fakeit_Slogan": localFaker.Slogan, + + // FakeIt / Hacker + "fakeit_HackerAbbreviation": localFaker.HackerAbbreviation, + "fakeit_HackerAdjective": localFaker.HackerAdjective, + "fakeit_HackerNoun": localFaker.HackerNoun, + "fakeit_HackerPhrase": localFaker.HackerPhrase, + "fakeit_HackerVerb": localFaker.HackerVerb, + + // FakeIt / Hipster + "fakeit_HipsterWord": localFaker.HipsterWord, + "fakeit_HipsterSentence": localFaker.HipsterSentence, + "fakeit_HipsterParagraph": localFaker.HipsterParagraph, + + // FakeIt / App + "fakeit_AppName": localFaker.AppName, + "fakeit_AppVersion": localFaker.AppVersion, + "fakeit_AppAuthor": localFaker.AppAuthor, + + // FakeIt / Animal + "fakeit_PetName": localFaker.PetName, + "fakeit_Animal": localFaker.Animal, + "fakeit_AnimalType": localFaker.AnimalType, + "fakeit_FarmAnimal": localFaker.FarmAnimal, + "fakeit_Cat": localFaker.Cat, + "fakeit_Dog": localFaker.Dog, + "fakeit_Bird": localFaker.Bird, + + // FakeIt / Emoji + "fakeit_Emoji": localFaker.Emoji, + "fakeit_EmojiDescription": localFaker.EmojiDescription, + "fakeit_EmojiCategory": localFaker.EmojiCategory, + "fakeit_EmojiAlias": localFaker.EmojiAlias, + "fakeit_EmojiTag": localFaker.EmojiTag, + + // FakeIt / Language + "fakeit_Language": localFaker.Language, + "fakeit_LanguageAbbreviation": localFaker.LanguageAbbreviation, + "fakeit_ProgrammingLanguage": localFaker.ProgrammingLanguage, + + // FakeIt / Number + "fakeit_Number": localFaker.Number, + "fakeit_Int": localFaker.Int, + "fakeit_IntN": localFaker.IntN, + "fakeit_Int8": localFaker.Int8, + "fakeit_Int16": localFaker.Int16, + "fakeit_Int32": localFaker.Int32, + "fakeit_Int64": localFaker.Int64, + "fakeit_Uint": localFaker.Uint, + "fakeit_UintN": localFaker.UintN, + "fakeit_Uint8": localFaker.Uint8, + "fakeit_Uint16": localFaker.Uint16, + "fakeit_Uint32": localFaker.Uint32, + "fakeit_Uint64": localFaker.Uint64, + "fakeit_Float32": localFaker.Float32, + "fakeit_Float32Range": localFaker.Float32Range, + "fakeit_Float64": localFaker.Float64, + "fakeit_Float64Range": localFaker.Float64Range, + "fakeit_HexUint": localFaker.HexUint, + + // FakeIt / String + "fakeit_Digit": localFaker.Digit, + "fakeit_DigitN": localFaker.DigitN, + "fakeit_Letter": localFaker.Letter, + "fakeit_LetterN": localFaker.LetterN, + "fakeit_Lexify": localFaker.Lexify, + "fakeit_Numerify": localFaker.Numerify, + + // FakeIt / Celebrity + "fakeit_CelebrityActor": localFaker.CelebrityActor, + "fakeit_CelebrityBusiness": localFaker.CelebrityBusiness, + "fakeit_CelebritySport": localFaker.CelebritySport, + + // FakeIt / Minecraft + "fakeit_MinecraftOre": localFaker.MinecraftOre, + "fakeit_MinecraftWood": localFaker.MinecraftWood, + "fakeit_MinecraftArmorTier": localFaker.MinecraftArmorTier, + "fakeit_MinecraftArmorPart": localFaker.MinecraftArmorPart, + "fakeit_MinecraftWeapon": localFaker.MinecraftWeapon, + "fakeit_MinecraftTool": localFaker.MinecraftTool, + "fakeit_MinecraftDye": localFaker.MinecraftDye, + "fakeit_MinecraftFood": localFaker.MinecraftFood, + "fakeit_MinecraftAnimal": localFaker.MinecraftAnimal, + "fakeit_MinecraftVillagerJob": localFaker.MinecraftVillagerJob, + "fakeit_MinecraftVillagerStation": localFaker.MinecraftVillagerStation, + "fakeit_MinecraftVillagerLevel": localFaker.MinecraftVillagerLevel, + "fakeit_MinecraftMobPassive": localFaker.MinecraftMobPassive, + "fakeit_MinecraftMobNeutral": localFaker.MinecraftMobNeutral, + "fakeit_MinecraftMobHostile": localFaker.MinecraftMobHostile, + "fakeit_MinecraftMobBoss": localFaker.MinecraftMobBoss, + "fakeit_MinecraftBiome": localFaker.MinecraftBiome, + "fakeit_MinecraftWeather": localFaker.MinecraftWeather, + + // FakeIt / Book + "fakeit_BookTitle": localFaker.BookTitle, + "fakeit_BookAuthor": localFaker.BookAuthor, + "fakeit_BookGenre": localFaker.BookGenre, + + // FakeIt / Movie + "fakeit_MovieName": localFaker.MovieName, + "fakeit_MovieGenre": localFaker.MovieGenre, + + // FakeIt / Error + "fakeit_Error": localFaker.Error, + "fakeit_ErrorDatabase": localFaker.ErrorDatabase, + "fakeit_ErrorGRPC": localFaker.ErrorGRPC, + "fakeit_ErrorHTTP": localFaker.ErrorHTTP, + "fakeit_ErrorHTTPClient": localFaker.ErrorHTTPClient, + "fakeit_ErrorHTTPServer": localFaker.ErrorHTTPServer, + "fakeit_ErrorRuntime": localFaker.ErrorRuntime, + + // FakeIt / School + "fakeit_School": localFaker.School, + + // FakeIt / Song + "fakeit_SongName": localFaker.SongName, + "fakeit_SongArtist": localFaker.SongArtist, + "fakeit_SongGenre": localFaker.SongGenre, + } +} From e5c681a22be9a32deb133a22a5979df1171c349c Mon Sep 17 00:00:00 2001 From: Aykhan Shahsuvarov Date: Fri, 30 May 2025 21:44:10 +0400 Subject: [PATCH 3/7] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20bump=20version=20to=20?= =?UTF-8?q?0.7.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index 322d160..072e50b 100644 --- a/config/config.go +++ b/config/config.go @@ -18,7 +18,7 @@ import ( ) const ( - VERSION string = "0.6.5" + VERSION string = "0.7.0" DefaultUserAgent string = "Dodo/" + VERSION DefaultMethod string = "GET" DefaultTimeout time.Duration = time.Second * 10 From 5c3e254e1e95a7c4a4d42608da3a8f0988e21b39 Mon Sep 17 00:00:00 2001 From: Aykhan Shahsuvarov Date: Fri, 30 May 2025 21:51:11 +0400 Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=93=9A=20Update=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 954f1df..a3112ff 100644 --- a/README.md +++ b/README.md @@ -314,4 +314,4 @@ In JSON config: } ``` -For the full list of template functions over 200 functions, refer to the `newFuncMap` function in `requests/request.go`. +For the full list of template functions over 200 functions, refer to the `NewFuncMap` function in `utils/templates.go`. From 9aaf2db74dd5356111fab2fd348ce9fb4de55bab Mon Sep 17 00:00:00 2001 From: Aykhan Shahsuvarov Date: Sun, 1 Jun 2025 20:52:06 +0400 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=93=9A=20Update=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a3112ff..0e04edf 100644 --- a/README.md +++ b/README.md @@ -277,7 +277,7 @@ In CLI config: ```sh dodo -u https://example.com -r 1 \ -header "User-Agent:{{ fakeit_UserAgent }}" \ # e.g. "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)" - -param "username={{ fakeit_Username }}" \ # e.g. "username=John Bob" + -param "username={{ strings_ToUpper fakeit_Username }}" \ # e.g. "username=JOHN BOB" -cookie "token={{ fakeit_Password true true true true true 10 }}" \ # e.g. token=1234567890abcdef1234567890abcdef -body '{"email":"{{ fakeit_Email }}", "password":"{{ fakeit_Password true true true true true 10 }}"}' # e.g. {"email":"john.doe@example.com", "password":"12rw4d-78d"} ``` @@ -298,7 +298,7 @@ params: body: - '{ "username": "{{ fakeit_Username }}", "password": "{{ fakeit_Password }}" }' # e.g. { "username": "john.doe", "password": "password123" } - - '{ "email": "{{ fakeit_Email }}", "phone": "{{ fakeit_Phone }}" }' # e.g. { "email": "john.doe@example.com", "phone": "+1234567890" } + - '{ "email": "{{ fakeit_Email }}", "phone": "{{ fakeit_Phone }}" }' # e.g. { "email": "john.doe@example.com", "phone": "1234567890" } ``` In JSON config: @@ -309,7 +309,7 @@ In JSON config: { "User-Agent": "{{ fakeit_UserAgent }}" }, // e.g. "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)" ], "body": [ - "{ \"username\": \"{{ fakeit_Username }}\", \"password\": \"{{ fakeit_Password }}\" }", // e.g. { "username": "john.doe", "password": "password123" } + "{ \"username\": \"{{ strings_RemoveSpaces fakeit_Username }}\", \"password\": \"{{ fakeit_Password }}\" }", // e.g. { "username": "johndoe", "password": "password123" } ], } ``` From a8cd253c632aabc3dc1cd3f100d306457ade1a0f Mon Sep 17 00:00:00 2001 From: Aykhan Shahsuvarov Date: Sun, 1 Jun 2025 20:52:27 +0400 Subject: [PATCH 6/7] =?UTF-8?q?=E2=9C=A8=20Add=20string=20functions=20to?= =?UTF-8?q?=20templates=20func=20map?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/templates.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/utils/templates.go b/utils/templates.go index d9fe610..8ecfc14 100644 --- a/utils/templates.go +++ b/utils/templates.go @@ -34,6 +34,26 @@ func NewFuncMap(localRand *rand.Rand) template.FuncMap { } return date }, + "strings_First": func(s string, n int) string { + if n >= len(s) { + return s + } + return s[:n] + }, + "strings_Last": func(s string, n int) string { + if n >= len(s) { + return s + } + return s[len(s)-n:] + }, + "strings_Truncate": func(s string, n int) string { + if n >= len(s) { + return s + } + return s[:n] + "..." + }, + "strings_TrimPrefix": strings.TrimPrefix, + "strings_TrimSuffix": strings.TrimSuffix, // FakeIt / Product "fakeit_ProductName": localFaker.ProductName, From 9152eefdc552c144e20679d964823b85cffd1443 Mon Sep 17 00:00:00 2001 From: Aykhan Shahsuvarov Date: Sun, 1 Jun 2025 23:22:55 +0400 Subject: [PATCH 7/7] =?UTF-8?q?=E2=9C=A8=20Add=20'body=5FFormData'=20gener?= =?UTF-8?q?ator=20to=20template=20func=20maps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 162 ++++++------ config/config.go | 8 +- requests/request.go | 62 ++++- utils/templates.go | 603 ++++++++++++++++++++++++-------------------- 4 files changed, 476 insertions(+), 359 deletions(-) diff --git a/README.md b/README.md index 0e04edf..208ce26 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@ - [Usage](#usage) - [1. CLI Usage](#1-cli-usage) - [2. Config File Usage](#2-config-file-usage) - - [2.1 JSON Example](#21-json-example) - - [2.2 YAML/YML Example](#22-yamlyml-example) + - [2.1 YAML/YML Example](#21-yamlyml-example) + - [2.2 JSON Example](#22-json-example) - [3. CLI & Config File Combination](#3-cli--config-file-combination) - [Config Parameters Reference](#config-parameters-reference) - [Template Functions](#template-functions) @@ -74,7 +74,84 @@ docker run --rm -i aykhans/dodo -u https://example.com -m GET -d 10 -r 1000 -o 1 Send 1000 GET requests to https://example.com with 10 parallel dodos (threads), each with a timeout of 800 milliseconds, within a maximum duration of 250 seconds: -#### 2.1 JSON Example +#### 2.1 YAML/YML Example + +```yaml +method: "GET" +url: "https://example.com" +yes: false +timeout: "800ms" +dodos: 10 +requests: 1000 +duration: "250s" +skip_verify: false + +params: + # A random value will be selected from the list for first "key1" param on each request + # And always "value" for second "key1" param on each request + # e.g. "?key1=value2&key1=value" + - key1: ["value1", "value2", "value3", "value4"] + - key1: "value" + + # A random value will be selected from the list for param "key2" on each request + # e.g. "?key2=value2" + - key2: ["value1", "value2"] + +headers: + # A random value will be selected from the list for first "key1" header on each request + # And always "value" for second "key1" header on each request + # e.g. "key1: value3", "key1: value" + - key1: ["value1", "value2", "value3", "value4"] + - key1: "value" + + # A random value will be selected from the list for header "key2" on each request + # e.g. "key2: value2" + - key2: ["value1", "value2"] + +cookies: + # A random value will be selected from the list for first "key1" cookie on each request + # And always "value" for second "key1" cookie on each request + # e.g. "key1=value4; key1=value" + - key1: ["value1", "value2", "value3", "value4"] + - key1: "value" + + # A random value will be selected from the list for cookie "key2" on each request + # e.g. "key2=value1" + - key2: ["value1", "value2"] + +body: "body-text" +# OR +# A random body value will be selected from the list for each request +body: + - "body-text1" + - "body-text2" + - "body-text3" + +proxy: "http://example.com:8080" +# OR +# A random proxy will be selected from the list for each request +proxy: + - "http://example.com:8080" + - "http://username:password@example.com:8080" + - "socks5://example.com:8080" + - "socks5h://example.com:8080" +``` + +```sh +dodo -f /path/config.yaml +# OR +dodo -f https://example.com/config.yaml +``` + +With Docker: + +```sh +docker run --rm -i -v /path/to/config.yaml:/config.yaml aykhans/dodo -f /config.yaml +# OR +docker run --rm -i aykhans/dodo -f https://example.com/config.yaml +``` + +#### 2.2 JSON Example ```jsonc { @@ -154,83 +231,6 @@ docker run --rm -i -v /path/to/config.json:/config.json aykhans/dodo docker run --rm -i aykhans/dodo -f https://example.com/config.json ``` -#### 2.2 YAML/YML Example - -```yaml -method: "GET" -url: "https://example.com" -yes: false -timeout: "800ms" -dodos: 10 -requests: 1000 -duration: "250s" -skip_verify: false - -params: - # A random value will be selected from the list for first "key1" param on each request - # And always "value" for second "key1" param on each request - # e.g. "?key1=value2&key1=value" - - key1: ["value1", "value2", "value3", "value4"] - - key1: "value" - - # A random value will be selected from the list for param "key2" on each request - # e.g. "?key2=value2" - - key2: ["value1", "value2"] - -headers: - # A random value will be selected from the list for first "key1" header on each request - # And always "value" for second "key1" header on each request - # e.g. "key1: value3", "key1: value" - - key1: ["value1", "value2", "value3", "value4"] - - key1: "value" - - # A random value will be selected from the list for header "key2" on each request - # e.g. "key2: value2" - - key2: ["value1", "value2"] - -cookies: - # A random value will be selected from the list for first "key1" cookie on each request - # And always "value" for second "key1" cookie on each request - # e.g. "key1=value4; key1=value" - - key1: ["value1", "value2", "value3", "value4"] - - key1: "value" - - # A random value will be selected from the list for cookie "key2" on each request - # e.g. "key2=value1" - - key2: ["value1", "value2"] - -body: "body-text" -# OR -# A random body value will be selected from the list for each request -body: - - "body-text1" - - "body-text2" - - "body-text3" - -proxy: "http://example.com:8080" -# OR -# A random proxy will be selected from the list for each request -proxy: - - "http://example.com:8080" - - "http://username:password@example.com:8080" - - "socks5://example.com:8080" - - "socks5h://example.com:8080" -``` - -```sh -dodo -f /path/config.yaml -# OR -dodo -f https://example.com/config.yaml -``` - -With Docker: - -```sh -docker run --rm -i -v /path/to/config.yaml:/config.yaml aykhans/dodo -f /config.yaml -# OR -docker run --rm -i aykhans/dodo -f https://example.com/config.yaml -``` - ### 3. CLI & Config File Combination CLI arguments override config file values: @@ -299,6 +299,7 @@ params: body: - '{ "username": "{{ fakeit_Username }}", "password": "{{ fakeit_Password }}" }' # e.g. { "username": "john.doe", "password": "password123" } - '{ "email": "{{ fakeit_Email }}", "phone": "{{ fakeit_Phone }}" }' # e.g. { "email": "john.doe@example.com", "phone": "1234567890" } + - '{{ body_FormData (dict_Str "username" fakeit_Username "password" "secret123") }}' # Creates multipart form data for form submissions, automatically sets the appropriate Content-Type header. ``` In JSON config: @@ -310,6 +311,7 @@ In JSON config: ], "body": [ "{ \"username\": \"{{ strings_RemoveSpaces fakeit_Username }}\", \"password\": \"{{ fakeit_Password }}\" }", // e.g. { "username": "johndoe", "password": "password123" } + "{{ body_FormData (dict_Str \"username\" fakeit_Username \"password\" \"12345\") }}", // Creates multipart form data for form submissions, automatically sets the appropriate Content-Type header. ], } ``` diff --git a/config/config.go b/config/config.go index 072e50b..35bb72c 100644 --- a/config/config.go +++ b/config/config.go @@ -204,7 +204,13 @@ func (config *Config) Validate() []error { } } - funcMap := utils.NewFuncMap(rand.New(rand.NewSource(time.Now().UnixNano()))) + funcMap := *utils.NewFuncMapGenerator( + rand.New( + rand.NewSource( + time.Now().UnixNano(), + ), + ), + ).GetFuncMap() for _, header := range config.Headers { t, err := template.New("default").Funcs(funcMap).Parse(header.Key) diff --git a/requests/request.go b/requests/request.go index 0e32e49..acd1a6a 100644 --- a/requests/request.go +++ b/requests/request.go @@ -111,16 +111,25 @@ func getRequestGeneratorFunc( getParams := getKeyValueGeneratorFunc(params, localRand) getHeaders := getKeyValueGeneratorFunc(headers, localRand) getCookies := getKeyValueGeneratorFunc(cookies, localRand) - getBody := getValueFunc(bodies, utils.NewFuncMap(localRand), localRand) + getBody := getBodyValueFunc(bodies, utils.NewFuncMapGenerator(localRand), localRand) return func() *fasthttp.Request { + body, contentType := getBody() + headers := getHeaders() + if contentType != "" { + headers = append(headers, types.KeyValue[string, string]{ + Key: "Content-Type", + Value: contentType, + }) + } + return newFasthttpRequest( URL, getParams(), - getHeaders(), + headers, getCookies(), method, - getBody(), + body, ) } } @@ -201,7 +210,7 @@ func getKeyValueGeneratorFunc[ ) func() T { keyValueGenerators := make([]keyValueGenerator, len(keyValueSlice)) - funcMap := utils.NewFuncMap(localRand) + funcMap := *utils.NewFuncMapGenerator(localRand).GetFuncMap() for i, kv := range keyValueSlice { keyValueGenerators[i] = keyValueGenerator{ @@ -285,3 +294,48 @@ func getValueFunc( } } } + +// getBodyValueFunc creates a function that randomly selects and processes a request body from a slice of templates. +// It returns a closure that generates both the body content and the appropriate Content-Type header value. +// +// Parameters: +// - values: A slice of string templates that can contain template directives for request bodies +// - funcMapGenerator: Provides template functions and content type information +// - localRand: A random number generator for consistent randomization +// +// The returned function, when called, will: +// 1. Select a random body template from the values slice +// 2. Execute the selected template with available template functions +// 3. Return both the processed body string and the appropriate Content-Type header value +// +// If the selected template is nil (due to earlier parsing failure), the function will return +// empty strings for both the body and Content-Type. +// +// This enables dynamic generation of request bodies with proper content type headers. +func getBodyValueFunc( + values []string, + funcMapGenerator *utils.FuncMapGenerator, + localRand *rand.Rand, +) func() (string, string) { + templates := make([]*template.Template, len(values)) + + for i, value := range values { + t, err := template.New("default").Funcs(*funcMapGenerator.GetFuncMap()).Parse(value) + if err != nil { + templates[i] = nil + } + templates[i] = t + } + + randomTemplateFunc := utils.RandomValueCycle(templates, localRand) + + return func() (string, string) { + if tmpl := randomTemplateFunc(); tmpl == nil { + return "", "" + } else { + var buf bytes.Buffer + _ = tmpl.Execute(&buf, nil) + return buf.String(), funcMapGenerator.GetBodyDataHeader() + } + } +} diff --git a/utils/templates.go b/utils/templates.go index 8ecfc14..085dae0 100644 --- a/utils/templates.go +++ b/utils/templates.go @@ -1,7 +1,9 @@ package utils import ( + "bytes" "math/rand" + "mime/multipart" "strings" "text/template" "time" @@ -9,6 +11,31 @@ import ( "github.com/brianvoe/gofakeit/v7" ) +type FuncMapGenerator struct { + bodyDataHeader string + localFaker *gofakeit.Faker + funcMap *template.FuncMap +} + +func NewFuncMapGenerator(localRand *rand.Rand) *FuncMapGenerator { + f := &FuncMapGenerator{ + localFaker: gofakeit.NewFaker(localRand, false), + } + f.funcMap = f.newFuncMap() + + return f +} + +func (g *FuncMapGenerator) GetBodyDataHeader() string { + tempHeader := g.bodyDataHeader + g.bodyDataHeader = "" + return tempHeader +} + +func (g *FuncMapGenerator) GetFuncMap() *template.FuncMap { + return g.funcMap +} + // NewFuncMap creates a template.FuncMap populated with string manipulation functions // and data generation functions from gofakeit. // @@ -17,11 +44,11 @@ import ( // // All functions are prefixed to avoid naming conflicts: // - String functions: "strings_*" +// - Dict functions: "dict_*" +// - Body functions: "body_*" // - Data generation functions: "fakeit_*" -func NewFuncMap(localRand *rand.Rand) template.FuncMap { - localFaker := gofakeit.NewFaker(localRand, false) - - return template.FuncMap{ +func (g *FuncMapGenerator) newFuncMap() *template.FuncMap { + return &template.FuncMap{ // Strings "strings_ToUpper": strings.ToUpper, "strings_ToLower": strings.ToLower, @@ -55,61 +82,89 @@ func NewFuncMap(localRand *rand.Rand) template.FuncMap { "strings_TrimPrefix": strings.TrimPrefix, "strings_TrimSuffix": strings.TrimSuffix, + // Dict + "dict_Str": func(values ...any) map[string]string { + dict := make(map[string]string) + for i := 0; i < len(values); i += 2 { + if i+1 < len(values) { + key := values[i].(string) + value := values[i+1].(string) + dict[key] = value + } + } + return dict + }, + + // Body + "body_FormData": func(kv map[string]string) string { + var data bytes.Buffer + writer := multipart.NewWriter(&data) + + for k, v := range kv { + _ = writer.WriteField(k, v) + } + + _ = writer.Close() + g.bodyDataHeader = writer.FormDataContentType() + + return data.String() + }, + // FakeIt / Product - "fakeit_ProductName": localFaker.ProductName, - "fakeit_ProductDescription": localFaker.ProductDescription, - "fakeit_ProductCategory": localFaker.ProductCategory, - "fakeit_ProductFeature": localFaker.ProductFeature, - "fakeit_ProductMaterial": localFaker.ProductMaterial, - "fakeit_ProductUPC": localFaker.ProductUPC, - "fakeit_ProductAudience": localFaker.ProductAudience, - "fakeit_ProductDimension": localFaker.ProductDimension, - "fakeit_ProductUseCase": localFaker.ProductUseCase, - "fakeit_ProductBenefit": localFaker.ProductBenefit, - "fakeit_ProductSuffix": localFaker.ProductSuffix, + "fakeit_ProductName": g.localFaker.ProductName, + "fakeit_ProductDescription": g.localFaker.ProductDescription, + "fakeit_ProductCategory": g.localFaker.ProductCategory, + "fakeit_ProductFeature": g.localFaker.ProductFeature, + "fakeit_ProductMaterial": g.localFaker.ProductMaterial, + "fakeit_ProductUPC": g.localFaker.ProductUPC, + "fakeit_ProductAudience": g.localFaker.ProductAudience, + "fakeit_ProductDimension": g.localFaker.ProductDimension, + "fakeit_ProductUseCase": g.localFaker.ProductUseCase, + "fakeit_ProductBenefit": g.localFaker.ProductBenefit, + "fakeit_ProductSuffix": g.localFaker.ProductSuffix, // FakeIt / Person - "fakeit_Name": localFaker.Name, - "fakeit_NamePrefix": localFaker.NamePrefix, - "fakeit_NameSuffix": localFaker.NameSuffix, - "fakeit_FirstName": localFaker.FirstName, - "fakeit_MiddleName": localFaker.MiddleName, - "fakeit_LastName": localFaker.LastName, - "fakeit_Gender": localFaker.Gender, - "fakeit_SSN": localFaker.SSN, - "fakeit_Hobby": localFaker.Hobby, - "fakeit_Email": localFaker.Email, - "fakeit_Phone": localFaker.Phone, - "fakeit_PhoneFormatted": localFaker.PhoneFormatted, + "fakeit_Name": g.localFaker.Name, + "fakeit_NamePrefix": g.localFaker.NamePrefix, + "fakeit_NameSuffix": g.localFaker.NameSuffix, + "fakeit_FirstName": g.localFaker.FirstName, + "fakeit_MiddleName": g.localFaker.MiddleName, + "fakeit_LastName": g.localFaker.LastName, + "fakeit_Gender": g.localFaker.Gender, + "fakeit_SSN": g.localFaker.SSN, + "fakeit_Hobby": g.localFaker.Hobby, + "fakeit_Email": g.localFaker.Email, + "fakeit_Phone": g.localFaker.Phone, + "fakeit_PhoneFormatted": g.localFaker.PhoneFormatted, // FakeIt / Auth - "fakeit_Username": localFaker.Username, - "fakeit_Password": localFaker.Password, + "fakeit_Username": g.localFaker.Username, + "fakeit_Password": g.localFaker.Password, // FakeIt / Address - "fakeit_City": localFaker.City, - "fakeit_Country": localFaker.Country, - "fakeit_CountryAbr": localFaker.CountryAbr, - "fakeit_State": localFaker.State, - "fakeit_StateAbr": localFaker.StateAbr, - "fakeit_Street": localFaker.Street, - "fakeit_StreetName": localFaker.StreetName, - "fakeit_StreetNumber": localFaker.StreetNumber, - "fakeit_StreetPrefix": localFaker.StreetPrefix, - "fakeit_StreetSuffix": localFaker.StreetSuffix, - "fakeit_Zip": localFaker.Zip, - "fakeit_Latitude": localFaker.Latitude, + "fakeit_City": g.localFaker.City, + "fakeit_Country": g.localFaker.Country, + "fakeit_CountryAbr": g.localFaker.CountryAbr, + "fakeit_State": g.localFaker.State, + "fakeit_StateAbr": g.localFaker.StateAbr, + "fakeit_Street": g.localFaker.Street, + "fakeit_StreetName": g.localFaker.StreetName, + "fakeit_StreetNumber": g.localFaker.StreetNumber, + "fakeit_StreetPrefix": g.localFaker.StreetPrefix, + "fakeit_StreetSuffix": g.localFaker.StreetSuffix, + "fakeit_Zip": g.localFaker.Zip, + "fakeit_Latitude": g.localFaker.Latitude, "fakeit_LatitudeInRange": func(min, max float64) float64 { - value, err := localFaker.LatitudeInRange(min, max) + value, err := g.localFaker.LatitudeInRange(min, max) if err != nil { var zero float64 return zero } return value }, - "fakeit_Longitude": localFaker.Longitude, + "fakeit_Longitude": g.localFaker.Longitude, "fakeit_LongitudeInRange": func(min, max float64) float64 { - value, err := localFaker.LongitudeInRange(min, max) + value, err := g.localFaker.LongitudeInRange(min, max) if err != nil { var zero float64 return zero @@ -118,289 +173,289 @@ func NewFuncMap(localRand *rand.Rand) template.FuncMap { }, // FakeIt / Game - "fakeit_Gamertag": localFaker.Gamertag, + "fakeit_Gamertag": g.localFaker.Gamertag, // FakeIt / Beer - "fakeit_BeerAlcohol": localFaker.BeerAlcohol, - "fakeit_BeerBlg": localFaker.BeerBlg, - "fakeit_BeerHop": localFaker.BeerHop, - "fakeit_BeerIbu": localFaker.BeerIbu, - "fakeit_BeerMalt": localFaker.BeerMalt, - "fakeit_BeerName": localFaker.BeerName, - "fakeit_BeerStyle": localFaker.BeerStyle, - "fakeit_BeerYeast": localFaker.BeerYeast, + "fakeit_BeerAlcohol": g.localFaker.BeerAlcohol, + "fakeit_BeerBlg": g.localFaker.BeerBlg, + "fakeit_BeerHop": g.localFaker.BeerHop, + "fakeit_BeerIbu": g.localFaker.BeerIbu, + "fakeit_BeerMalt": g.localFaker.BeerMalt, + "fakeit_BeerName": g.localFaker.BeerName, + "fakeit_BeerStyle": g.localFaker.BeerStyle, + "fakeit_BeerYeast": g.localFaker.BeerYeast, // FakeIt / Car - "fakeit_CarMaker": localFaker.CarMaker, - "fakeit_CarModel": localFaker.CarModel, - "fakeit_CarType": localFaker.CarType, - "fakeit_CarFuelType": localFaker.CarFuelType, - "fakeit_CarTransmissionType": localFaker.CarTransmissionType, + "fakeit_CarMaker": g.localFaker.CarMaker, + "fakeit_CarModel": g.localFaker.CarModel, + "fakeit_CarType": g.localFaker.CarType, + "fakeit_CarFuelType": g.localFaker.CarFuelType, + "fakeit_CarTransmissionType": g.localFaker.CarTransmissionType, // FakeIt / Words - "fakeit_Noun": localFaker.Noun, - "fakeit_NounCommon": localFaker.NounCommon, - "fakeit_NounConcrete": localFaker.NounConcrete, - "fakeit_NounAbstract": localFaker.NounAbstract, - "fakeit_NounCollectivePeople": localFaker.NounCollectivePeople, - "fakeit_NounCollectiveAnimal": localFaker.NounCollectiveAnimal, - "fakeit_NounCollectiveThing": localFaker.NounCollectiveThing, - "fakeit_NounCountable": localFaker.NounCountable, - "fakeit_NounUncountable": localFaker.NounUncountable, - "fakeit_Verb": localFaker.Verb, - "fakeit_VerbAction": localFaker.VerbAction, - "fakeit_VerbLinking": localFaker.VerbLinking, - "fakeit_VerbHelping": localFaker.VerbHelping, - "fakeit_Adverb": localFaker.Adverb, - "fakeit_AdverbManner": localFaker.AdverbManner, - "fakeit_AdverbDegree": localFaker.AdverbDegree, - "fakeit_AdverbPlace": localFaker.AdverbPlace, - "fakeit_AdverbTimeDefinite": localFaker.AdverbTimeDefinite, - "fakeit_AdverbTimeIndefinite": localFaker.AdverbTimeIndefinite, - "fakeit_AdverbFrequencyDefinite": localFaker.AdverbFrequencyDefinite, - "fakeit_AdverbFrequencyIndefinite": localFaker.AdverbFrequencyIndefinite, - "fakeit_Preposition": localFaker.Preposition, - "fakeit_PrepositionSimple": localFaker.PrepositionSimple, - "fakeit_PrepositionDouble": localFaker.PrepositionDouble, - "fakeit_PrepositionCompound": localFaker.PrepositionCompound, - "fakeit_Adjective": localFaker.Adjective, - "fakeit_AdjectiveDescriptive": localFaker.AdjectiveDescriptive, - "fakeit_AdjectiveQuantitative": localFaker.AdjectiveQuantitative, - "fakeit_AdjectiveProper": localFaker.AdjectiveProper, - "fakeit_AdjectiveDemonstrative": localFaker.AdjectiveDemonstrative, - "fakeit_AdjectivePossessive": localFaker.AdjectivePossessive, - "fakeit_AdjectiveInterrogative": localFaker.AdjectiveInterrogative, - "fakeit_AdjectiveIndefinite": localFaker.AdjectiveIndefinite, - "fakeit_Pronoun": localFaker.Pronoun, - "fakeit_PronounPersonal": localFaker.PronounPersonal, - "fakeit_PronounObject": localFaker.PronounObject, - "fakeit_PronounPossessive": localFaker.PronounPossessive, - "fakeit_PronounReflective": localFaker.PronounReflective, - "fakeit_PronounDemonstrative": localFaker.PronounDemonstrative, - "fakeit_PronounInterrogative": localFaker.PronounInterrogative, - "fakeit_PronounRelative": localFaker.PronounRelative, - "fakeit_Connective": localFaker.Connective, - "fakeit_ConnectiveTime": localFaker.ConnectiveTime, - "fakeit_ConnectiveComparative": localFaker.ConnectiveComparative, - "fakeit_ConnectiveComplaint": localFaker.ConnectiveComplaint, - "fakeit_ConnectiveListing": localFaker.ConnectiveListing, - "fakeit_ConnectiveCasual": localFaker.ConnectiveCasual, - "fakeit_ConnectiveExamplify": localFaker.ConnectiveExamplify, - "fakeit_Word": localFaker.Word, - "fakeit_Sentence": localFaker.Sentence, - "fakeit_Paragraph": localFaker.Paragraph, - "fakeit_LoremIpsumWord": localFaker.LoremIpsumWord, - "fakeit_LoremIpsumSentence": localFaker.LoremIpsumSentence, - "fakeit_LoremIpsumParagraph": localFaker.LoremIpsumParagraph, - "fakeit_Question": localFaker.Question, - "fakeit_Quote": localFaker.Quote, - "fakeit_Phrase": localFaker.Phrase, + "fakeit_Noun": g.localFaker.Noun, + "fakeit_NounCommon": g.localFaker.NounCommon, + "fakeit_NounConcrete": g.localFaker.NounConcrete, + "fakeit_NounAbstract": g.localFaker.NounAbstract, + "fakeit_NounCollectivePeople": g.localFaker.NounCollectivePeople, + "fakeit_NounCollectiveAnimal": g.localFaker.NounCollectiveAnimal, + "fakeit_NounCollectiveThing": g.localFaker.NounCollectiveThing, + "fakeit_NounCountable": g.localFaker.NounCountable, + "fakeit_NounUncountable": g.localFaker.NounUncountable, + "fakeit_Verb": g.localFaker.Verb, + "fakeit_VerbAction": g.localFaker.VerbAction, + "fakeit_VerbLinking": g.localFaker.VerbLinking, + "fakeit_VerbHelping": g.localFaker.VerbHelping, + "fakeit_Adverb": g.localFaker.Adverb, + "fakeit_AdverbManner": g.localFaker.AdverbManner, + "fakeit_AdverbDegree": g.localFaker.AdverbDegree, + "fakeit_AdverbPlace": g.localFaker.AdverbPlace, + "fakeit_AdverbTimeDefinite": g.localFaker.AdverbTimeDefinite, + "fakeit_AdverbTimeIndefinite": g.localFaker.AdverbTimeIndefinite, + "fakeit_AdverbFrequencyDefinite": g.localFaker.AdverbFrequencyDefinite, + "fakeit_AdverbFrequencyIndefinite": g.localFaker.AdverbFrequencyIndefinite, + "fakeit_Preposition": g.localFaker.Preposition, + "fakeit_PrepositionSimple": g.localFaker.PrepositionSimple, + "fakeit_PrepositionDouble": g.localFaker.PrepositionDouble, + "fakeit_PrepositionCompound": g.localFaker.PrepositionCompound, + "fakeit_Adjective": g.localFaker.Adjective, + "fakeit_AdjectiveDescriptive": g.localFaker.AdjectiveDescriptive, + "fakeit_AdjectiveQuantitative": g.localFaker.AdjectiveQuantitative, + "fakeit_AdjectiveProper": g.localFaker.AdjectiveProper, + "fakeit_AdjectiveDemonstrative": g.localFaker.AdjectiveDemonstrative, + "fakeit_AdjectivePossessive": g.localFaker.AdjectivePossessive, + "fakeit_AdjectiveInterrogative": g.localFaker.AdjectiveInterrogative, + "fakeit_AdjectiveIndefinite": g.localFaker.AdjectiveIndefinite, + "fakeit_Pronoun": g.localFaker.Pronoun, + "fakeit_PronounPersonal": g.localFaker.PronounPersonal, + "fakeit_PronounObject": g.localFaker.PronounObject, + "fakeit_PronounPossessive": g.localFaker.PronounPossessive, + "fakeit_PronounReflective": g.localFaker.PronounReflective, + "fakeit_PronounDemonstrative": g.localFaker.PronounDemonstrative, + "fakeit_PronounInterrogative": g.localFaker.PronounInterrogative, + "fakeit_PronounRelative": g.localFaker.PronounRelative, + "fakeit_Connective": g.localFaker.Connective, + "fakeit_ConnectiveTime": g.localFaker.ConnectiveTime, + "fakeit_ConnectiveComparative": g.localFaker.ConnectiveComparative, + "fakeit_ConnectiveComplaint": g.localFaker.ConnectiveComplaint, + "fakeit_ConnectiveListing": g.localFaker.ConnectiveListing, + "fakeit_ConnectiveCasual": g.localFaker.ConnectiveCasual, + "fakeit_ConnectiveExamplify": g.localFaker.ConnectiveExamplify, + "fakeit_Word": g.localFaker.Word, + "fakeit_Sentence": g.localFaker.Sentence, + "fakeit_Paragraph": g.localFaker.Paragraph, + "fakeit_LoremIpsumWord": g.localFaker.LoremIpsumWord, + "fakeit_LoremIpsumSentence": g.localFaker.LoremIpsumSentence, + "fakeit_LoremIpsumParagraph": g.localFaker.LoremIpsumParagraph, + "fakeit_Question": g.localFaker.Question, + "fakeit_Quote": g.localFaker.Quote, + "fakeit_Phrase": g.localFaker.Phrase, // FakeIt / Foods - "fakeit_Fruit": localFaker.Fruit, - "fakeit_Vegetable": localFaker.Vegetable, - "fakeit_Breakfast": localFaker.Breakfast, - "fakeit_Lunch": localFaker.Lunch, - "fakeit_Dinner": localFaker.Dinner, - "fakeit_Snack": localFaker.Snack, - "fakeit_Dessert": localFaker.Dessert, + "fakeit_Fruit": g.localFaker.Fruit, + "fakeit_Vegetable": g.localFaker.Vegetable, + "fakeit_Breakfast": g.localFaker.Breakfast, + "fakeit_Lunch": g.localFaker.Lunch, + "fakeit_Dinner": g.localFaker.Dinner, + "fakeit_Snack": g.localFaker.Snack, + "fakeit_Dessert": g.localFaker.Dessert, // FakeIt / Misc - "fakeit_Bool": localFaker.Bool, - "fakeit_UUID": localFaker.UUID, - "fakeit_FlipACoin": localFaker.FlipACoin, + "fakeit_Bool": g.localFaker.Bool, + "fakeit_UUID": g.localFaker.UUID, + "fakeit_FlipACoin": g.localFaker.FlipACoin, // FakeIt / Colors - "fakeit_Color": localFaker.Color, - "fakeit_HexColor": localFaker.HexColor, - "fakeit_RGBColor": localFaker.RGBColor, - "fakeit_SafeColor": localFaker.SafeColor, - "fakeit_NiceColors": localFaker.NiceColors, + "fakeit_Color": g.localFaker.Color, + "fakeit_HexColor": g.localFaker.HexColor, + "fakeit_RGBColor": g.localFaker.RGBColor, + "fakeit_SafeColor": g.localFaker.SafeColor, + "fakeit_NiceColors": g.localFaker.NiceColors, // FakeIt / Internet - "fakeit_URL": localFaker.URL, - "fakeit_DomainName": localFaker.DomainName, - "fakeit_DomainSuffix": localFaker.DomainSuffix, - "fakeit_IPv4Address": localFaker.IPv4Address, - "fakeit_IPv6Address": localFaker.IPv6Address, - "fakeit_MacAddress": localFaker.MacAddress, - "fakeit_HTTPStatusCode": localFaker.HTTPStatusCode, - "fakeit_HTTPStatusCodeSimple": localFaker.HTTPStatusCodeSimple, - "fakeit_LogLevel": localFaker.LogLevel, - "fakeit_HTTPMethod": localFaker.HTTPMethod, - "fakeit_HTTPVersion": localFaker.HTTPVersion, - "fakeit_UserAgent": localFaker.UserAgent, - "fakeit_ChromeUserAgent": localFaker.ChromeUserAgent, - "fakeit_FirefoxUserAgent": localFaker.FirefoxUserAgent, - "fakeit_OperaUserAgent": localFaker.OperaUserAgent, - "fakeit_SafariUserAgent": localFaker.SafariUserAgent, + "fakeit_URL": g.localFaker.URL, + "fakeit_DomainName": g.localFaker.DomainName, + "fakeit_DomainSuffix": g.localFaker.DomainSuffix, + "fakeit_IPv4Address": g.localFaker.IPv4Address, + "fakeit_IPv6Address": g.localFaker.IPv6Address, + "fakeit_MacAddress": g.localFaker.MacAddress, + "fakeit_HTTPStatusCode": g.localFaker.HTTPStatusCode, + "fakeit_HTTPStatusCodeSimple": g.localFaker.HTTPStatusCodeSimple, + "fakeit_LogLevel": g.localFaker.LogLevel, + "fakeit_HTTPMethod": g.localFaker.HTTPMethod, + "fakeit_HTTPVersion": g.localFaker.HTTPVersion, + "fakeit_UserAgent": g.localFaker.UserAgent, + "fakeit_ChromeUserAgent": g.localFaker.ChromeUserAgent, + "fakeit_FirefoxUserAgent": g.localFaker.FirefoxUserAgent, + "fakeit_OperaUserAgent": g.localFaker.OperaUserAgent, + "fakeit_SafariUserAgent": g.localFaker.SafariUserAgent, // FakeIt / HTML - "fakeit_InputName": localFaker.InputName, + "fakeit_InputName": g.localFaker.InputName, // FakeIt / Date/Time - "fakeit_Date": localFaker.Date, - "fakeit_PastDate": localFaker.PastDate, - "fakeit_FutureDate": localFaker.FutureDate, - "fakeit_DateRange": localFaker.DateRange, - "fakeit_NanoSecond": localFaker.NanoSecond, - "fakeit_Second": localFaker.Second, - "fakeit_Minute": localFaker.Minute, - "fakeit_Hour": localFaker.Hour, - "fakeit_Month": localFaker.Month, - "fakeit_MonthString": localFaker.MonthString, - "fakeit_Day": localFaker.Day, - "fakeit_WeekDay": localFaker.WeekDay, - "fakeit_Year": localFaker.Year, - "fakeit_TimeZone": localFaker.TimeZone, - "fakeit_TimeZoneAbv": localFaker.TimeZoneAbv, - "fakeit_TimeZoneFull": localFaker.TimeZoneFull, - "fakeit_TimeZoneOffset": localFaker.TimeZoneOffset, - "fakeit_TimeZoneRegion": localFaker.TimeZoneRegion, + "fakeit_Date": g.localFaker.Date, + "fakeit_PastDate": g.localFaker.PastDate, + "fakeit_FutureDate": g.localFaker.FutureDate, + "fakeit_DateRange": g.localFaker.DateRange, + "fakeit_NanoSecond": g.localFaker.NanoSecond, + "fakeit_Second": g.localFaker.Second, + "fakeit_Minute": g.localFaker.Minute, + "fakeit_Hour": g.localFaker.Hour, + "fakeit_Month": g.localFaker.Month, + "fakeit_MonthString": g.localFaker.MonthString, + "fakeit_Day": g.localFaker.Day, + "fakeit_WeekDay": g.localFaker.WeekDay, + "fakeit_Year": g.localFaker.Year, + "fakeit_TimeZone": g.localFaker.TimeZone, + "fakeit_TimeZoneAbv": g.localFaker.TimeZoneAbv, + "fakeit_TimeZoneFull": g.localFaker.TimeZoneFull, + "fakeit_TimeZoneOffset": g.localFaker.TimeZoneOffset, + "fakeit_TimeZoneRegion": g.localFaker.TimeZoneRegion, // FakeIt / Payment - "fakeit_Price": localFaker.Price, - "fakeit_CreditCardCvv": localFaker.CreditCardCvv, - "fakeit_CreditCardExp": localFaker.CreditCardExp, - "fakeit_CreditCardNumber": localFaker.CreditCardNumber, - "fakeit_CreditCardType": localFaker.CreditCardType, - "fakeit_CurrencyLong": localFaker.CurrencyLong, - "fakeit_CurrencyShort": localFaker.CurrencyShort, - "fakeit_AchRouting": localFaker.AchRouting, - "fakeit_AchAccount": localFaker.AchAccount, - "fakeit_BitcoinAddress": localFaker.BitcoinAddress, - "fakeit_BitcoinPrivateKey": localFaker.BitcoinPrivateKey, + "fakeit_Price": g.localFaker.Price, + "fakeit_CreditCardCvv": g.localFaker.CreditCardCvv, + "fakeit_CreditCardExp": g.localFaker.CreditCardExp, + "fakeit_CreditCardNumber": g.localFaker.CreditCardNumber, + "fakeit_CreditCardType": g.localFaker.CreditCardType, + "fakeit_CurrencyLong": g.localFaker.CurrencyLong, + "fakeit_CurrencyShort": g.localFaker.CurrencyShort, + "fakeit_AchRouting": g.localFaker.AchRouting, + "fakeit_AchAccount": g.localFaker.AchAccount, + "fakeit_BitcoinAddress": g.localFaker.BitcoinAddress, + "fakeit_BitcoinPrivateKey": g.localFaker.BitcoinPrivateKey, // FakeIt / Finance - "fakeit_Cusip": localFaker.Cusip, - "fakeit_Isin": localFaker.Isin, + "fakeit_Cusip": g.localFaker.Cusip, + "fakeit_Isin": g.localFaker.Isin, // FakeIt / Company - "fakeit_BS": localFaker.BS, - "fakeit_Blurb": localFaker.Blurb, - "fakeit_BuzzWord": localFaker.BuzzWord, - "fakeit_Company": localFaker.Company, - "fakeit_CompanySuffix": localFaker.CompanySuffix, - "fakeit_JobDescriptor": localFaker.JobDescriptor, - "fakeit_JobLevel": localFaker.JobLevel, - "fakeit_JobTitle": localFaker.JobTitle, - "fakeit_Slogan": localFaker.Slogan, + "fakeit_BS": g.localFaker.BS, + "fakeit_Blurb": g.localFaker.Blurb, + "fakeit_BuzzWord": g.localFaker.BuzzWord, + "fakeit_Company": g.localFaker.Company, + "fakeit_CompanySuffix": g.localFaker.CompanySuffix, + "fakeit_JobDescriptor": g.localFaker.JobDescriptor, + "fakeit_JobLevel": g.localFaker.JobLevel, + "fakeit_JobTitle": g.localFaker.JobTitle, + "fakeit_Slogan": g.localFaker.Slogan, // FakeIt / Hacker - "fakeit_HackerAbbreviation": localFaker.HackerAbbreviation, - "fakeit_HackerAdjective": localFaker.HackerAdjective, - "fakeit_HackerNoun": localFaker.HackerNoun, - "fakeit_HackerPhrase": localFaker.HackerPhrase, - "fakeit_HackerVerb": localFaker.HackerVerb, + "fakeit_HackerAbbreviation": g.localFaker.HackerAbbreviation, + "fakeit_HackerAdjective": g.localFaker.HackerAdjective, + "fakeit_HackerNoun": g.localFaker.HackerNoun, + "fakeit_HackerPhrase": g.localFaker.HackerPhrase, + "fakeit_HackerVerb": g.localFaker.HackerVerb, // FakeIt / Hipster - "fakeit_HipsterWord": localFaker.HipsterWord, - "fakeit_HipsterSentence": localFaker.HipsterSentence, - "fakeit_HipsterParagraph": localFaker.HipsterParagraph, + "fakeit_HipsterWord": g.localFaker.HipsterWord, + "fakeit_HipsterSentence": g.localFaker.HipsterSentence, + "fakeit_HipsterParagraph": g.localFaker.HipsterParagraph, // FakeIt / App - "fakeit_AppName": localFaker.AppName, - "fakeit_AppVersion": localFaker.AppVersion, - "fakeit_AppAuthor": localFaker.AppAuthor, + "fakeit_AppName": g.localFaker.AppName, + "fakeit_AppVersion": g.localFaker.AppVersion, + "fakeit_AppAuthor": g.localFaker.AppAuthor, // FakeIt / Animal - "fakeit_PetName": localFaker.PetName, - "fakeit_Animal": localFaker.Animal, - "fakeit_AnimalType": localFaker.AnimalType, - "fakeit_FarmAnimal": localFaker.FarmAnimal, - "fakeit_Cat": localFaker.Cat, - "fakeit_Dog": localFaker.Dog, - "fakeit_Bird": localFaker.Bird, + "fakeit_PetName": g.localFaker.PetName, + "fakeit_Animal": g.localFaker.Animal, + "fakeit_AnimalType": g.localFaker.AnimalType, + "fakeit_FarmAnimal": g.localFaker.FarmAnimal, + "fakeit_Cat": g.localFaker.Cat, + "fakeit_Dog": g.localFaker.Dog, + "fakeit_Bird": g.localFaker.Bird, // FakeIt / Emoji - "fakeit_Emoji": localFaker.Emoji, - "fakeit_EmojiDescription": localFaker.EmojiDescription, - "fakeit_EmojiCategory": localFaker.EmojiCategory, - "fakeit_EmojiAlias": localFaker.EmojiAlias, - "fakeit_EmojiTag": localFaker.EmojiTag, + "fakeit_Emoji": g.localFaker.Emoji, + "fakeit_EmojiDescription": g.localFaker.EmojiDescription, + "fakeit_EmojiCategory": g.localFaker.EmojiCategory, + "fakeit_EmojiAlias": g.localFaker.EmojiAlias, + "fakeit_EmojiTag": g.localFaker.EmojiTag, // FakeIt / Language - "fakeit_Language": localFaker.Language, - "fakeit_LanguageAbbreviation": localFaker.LanguageAbbreviation, - "fakeit_ProgrammingLanguage": localFaker.ProgrammingLanguage, + "fakeit_Language": g.localFaker.Language, + "fakeit_LanguageAbbreviation": g.localFaker.LanguageAbbreviation, + "fakeit_ProgrammingLanguage": g.localFaker.ProgrammingLanguage, // FakeIt / Number - "fakeit_Number": localFaker.Number, - "fakeit_Int": localFaker.Int, - "fakeit_IntN": localFaker.IntN, - "fakeit_Int8": localFaker.Int8, - "fakeit_Int16": localFaker.Int16, - "fakeit_Int32": localFaker.Int32, - "fakeit_Int64": localFaker.Int64, - "fakeit_Uint": localFaker.Uint, - "fakeit_UintN": localFaker.UintN, - "fakeit_Uint8": localFaker.Uint8, - "fakeit_Uint16": localFaker.Uint16, - "fakeit_Uint32": localFaker.Uint32, - "fakeit_Uint64": localFaker.Uint64, - "fakeit_Float32": localFaker.Float32, - "fakeit_Float32Range": localFaker.Float32Range, - "fakeit_Float64": localFaker.Float64, - "fakeit_Float64Range": localFaker.Float64Range, - "fakeit_HexUint": localFaker.HexUint, + "fakeit_Number": g.localFaker.Number, + "fakeit_Int": g.localFaker.Int, + "fakeit_IntN": g.localFaker.IntN, + "fakeit_Int8": g.localFaker.Int8, + "fakeit_Int16": g.localFaker.Int16, + "fakeit_Int32": g.localFaker.Int32, + "fakeit_Int64": g.localFaker.Int64, + "fakeit_Uint": g.localFaker.Uint, + "fakeit_UintN": g.localFaker.UintN, + "fakeit_Uint8": g.localFaker.Uint8, + "fakeit_Uint16": g.localFaker.Uint16, + "fakeit_Uint32": g.localFaker.Uint32, + "fakeit_Uint64": g.localFaker.Uint64, + "fakeit_Float32": g.localFaker.Float32, + "fakeit_Float32Range": g.localFaker.Float32Range, + "fakeit_Float64": g.localFaker.Float64, + "fakeit_Float64Range": g.localFaker.Float64Range, + "fakeit_HexUint": g.localFaker.HexUint, // FakeIt / String - "fakeit_Digit": localFaker.Digit, - "fakeit_DigitN": localFaker.DigitN, - "fakeit_Letter": localFaker.Letter, - "fakeit_LetterN": localFaker.LetterN, - "fakeit_Lexify": localFaker.Lexify, - "fakeit_Numerify": localFaker.Numerify, + "fakeit_Digit": g.localFaker.Digit, + "fakeit_DigitN": g.localFaker.DigitN, + "fakeit_Letter": g.localFaker.Letter, + "fakeit_LetterN": g.localFaker.LetterN, + "fakeit_Lexify": g.localFaker.Lexify, + "fakeit_Numerify": g.localFaker.Numerify, // FakeIt / Celebrity - "fakeit_CelebrityActor": localFaker.CelebrityActor, - "fakeit_CelebrityBusiness": localFaker.CelebrityBusiness, - "fakeit_CelebritySport": localFaker.CelebritySport, + "fakeit_CelebrityActor": g.localFaker.CelebrityActor, + "fakeit_CelebrityBusiness": g.localFaker.CelebrityBusiness, + "fakeit_CelebritySport": g.localFaker.CelebritySport, // FakeIt / Minecraft - "fakeit_MinecraftOre": localFaker.MinecraftOre, - "fakeit_MinecraftWood": localFaker.MinecraftWood, - "fakeit_MinecraftArmorTier": localFaker.MinecraftArmorTier, - "fakeit_MinecraftArmorPart": localFaker.MinecraftArmorPart, - "fakeit_MinecraftWeapon": localFaker.MinecraftWeapon, - "fakeit_MinecraftTool": localFaker.MinecraftTool, - "fakeit_MinecraftDye": localFaker.MinecraftDye, - "fakeit_MinecraftFood": localFaker.MinecraftFood, - "fakeit_MinecraftAnimal": localFaker.MinecraftAnimal, - "fakeit_MinecraftVillagerJob": localFaker.MinecraftVillagerJob, - "fakeit_MinecraftVillagerStation": localFaker.MinecraftVillagerStation, - "fakeit_MinecraftVillagerLevel": localFaker.MinecraftVillagerLevel, - "fakeit_MinecraftMobPassive": localFaker.MinecraftMobPassive, - "fakeit_MinecraftMobNeutral": localFaker.MinecraftMobNeutral, - "fakeit_MinecraftMobHostile": localFaker.MinecraftMobHostile, - "fakeit_MinecraftMobBoss": localFaker.MinecraftMobBoss, - "fakeit_MinecraftBiome": localFaker.MinecraftBiome, - "fakeit_MinecraftWeather": localFaker.MinecraftWeather, + "fakeit_MinecraftOre": g.localFaker.MinecraftOre, + "fakeit_MinecraftWood": g.localFaker.MinecraftWood, + "fakeit_MinecraftArmorTier": g.localFaker.MinecraftArmorTier, + "fakeit_MinecraftArmorPart": g.localFaker.MinecraftArmorPart, + "fakeit_MinecraftWeapon": g.localFaker.MinecraftWeapon, + "fakeit_MinecraftTool": g.localFaker.MinecraftTool, + "fakeit_MinecraftDye": g.localFaker.MinecraftDye, + "fakeit_MinecraftFood": g.localFaker.MinecraftFood, + "fakeit_MinecraftAnimal": g.localFaker.MinecraftAnimal, + "fakeit_MinecraftVillagerJob": g.localFaker.MinecraftVillagerJob, + "fakeit_MinecraftVillagerStation": g.localFaker.MinecraftVillagerStation, + "fakeit_MinecraftVillagerLevel": g.localFaker.MinecraftVillagerLevel, + "fakeit_MinecraftMobPassive": g.localFaker.MinecraftMobPassive, + "fakeit_MinecraftMobNeutral": g.localFaker.MinecraftMobNeutral, + "fakeit_MinecraftMobHostile": g.localFaker.MinecraftMobHostile, + "fakeit_MinecraftMobBoss": g.localFaker.MinecraftMobBoss, + "fakeit_MinecraftBiome": g.localFaker.MinecraftBiome, + "fakeit_MinecraftWeather": g.localFaker.MinecraftWeather, // FakeIt / Book - "fakeit_BookTitle": localFaker.BookTitle, - "fakeit_BookAuthor": localFaker.BookAuthor, - "fakeit_BookGenre": localFaker.BookGenre, + "fakeit_BookTitle": g.localFaker.BookTitle, + "fakeit_BookAuthor": g.localFaker.BookAuthor, + "fakeit_BookGenre": g.localFaker.BookGenre, // FakeIt / Movie - "fakeit_MovieName": localFaker.MovieName, - "fakeit_MovieGenre": localFaker.MovieGenre, + "fakeit_MovieName": g.localFaker.MovieName, + "fakeit_MovieGenre": g.localFaker.MovieGenre, // FakeIt / Error - "fakeit_Error": localFaker.Error, - "fakeit_ErrorDatabase": localFaker.ErrorDatabase, - "fakeit_ErrorGRPC": localFaker.ErrorGRPC, - "fakeit_ErrorHTTP": localFaker.ErrorHTTP, - "fakeit_ErrorHTTPClient": localFaker.ErrorHTTPClient, - "fakeit_ErrorHTTPServer": localFaker.ErrorHTTPServer, - "fakeit_ErrorRuntime": localFaker.ErrorRuntime, + "fakeit_Error": g.localFaker.Error, + "fakeit_ErrorDatabase": g.localFaker.ErrorDatabase, + "fakeit_ErrorGRPC": g.localFaker.ErrorGRPC, + "fakeit_ErrorHTTP": g.localFaker.ErrorHTTP, + "fakeit_ErrorHTTPClient": g.localFaker.ErrorHTTPClient, + "fakeit_ErrorHTTPServer": g.localFaker.ErrorHTTPServer, + "fakeit_ErrorRuntime": g.localFaker.ErrorRuntime, // FakeIt / School - "fakeit_School": localFaker.School, + "fakeit_School": g.localFaker.School, // FakeIt / Song - "fakeit_SongName": localFaker.SongName, - "fakeit_SongArtist": localFaker.SongArtist, - "fakeit_SongGenre": localFaker.SongGenre, + "fakeit_SongName": g.localFaker.SongName, + "fakeit_SongArtist": g.localFaker.SongArtist, + "fakeit_SongGenre": g.localFaker.SongGenre, } }