Repository: redpanda-data/connect (language: go)

Issue:
Bloblang method to generate fake data using Faker
## Overview
[Faker](https://github.com/bxcodec/faker) is a Golang library to generate fake data based on a struct. We want to generate a bunch of randomized test data and send those messages to a Kafka topic for performance testing and we currently use a simple Go app using the faker library. We would like to switch to using a Benthos pipeline to generate this data, so having a bloblang function that could do this would be helpful.

## Example
```
let x = {"foo":"bar"}
root = $x.fake()
```

Doing the above would replace "bar" with a randomly generate string. If instead of "bar" it was an integer, it would generate a random int value for foo. 
A lot of Faker's functionality comes from using tags in the struct, so I am not sure how we could leverage that, but there is fair amount that can be done without tags. Here's an [example](https://github.com/bxcodec/faker/blob/master/example_without_tag_test.go) of what can be done with Faker without using tags.



Discussion / hints:

Fix patch:
diff --git a/go.mod b/go.mod
index d588258070..08ded79bff 100644
--- a/go.mod
+++ b/go.mod
@@ -34,6 +34,7 @@ require (
 	github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.9.1 // indirect
 	github.com/benhoyt/goawk v1.17.1
 	github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d
+	github.com/bxcodec/faker/v3 v3.8.0
 	github.com/cenkalti/backoff/v4 v4.1.2
 	github.com/cespare/xxhash/v2 v2.1.2 // indirect
 	github.com/clbanning/mxj/v2 v2.5.5
diff --git a/go.sum b/go.sum
index f731a77eb7..f98a5634d6 100644
--- a/go.sum
+++ b/go.sum
@@ -269,6 +269,8 @@ github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D
 github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
 github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw=
 github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
+github.com/bxcodec/faker/v3 v3.8.0 h1:F59Qqnsh0BOtZRC+c4cXoB/VNYDMS3R5mlSpxIap1oU=
+github.com/bxcodec/faker/v3 v3.8.0/go.mod h1:gF31YgnMSMKgkvl+fyEo1xuSMbEuieyqfeslGYFjneM=
 github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo=
 github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
diff --git a/internal/bloblang/query/docs.go b/internal/bloblang/query/docs.go
index aeb4decdbe..532d556160 100644
--- a/internal/bloblang/query/docs.go
+++ b/internal/bloblang/query/docs.go
@@ -46,6 +46,7 @@ var (
 	FunctionCategoryEnvironment = "Environment"
 	FunctionCategoryDeprecated  = "Deprecated"
 	FunctionCategoryPlugin      = "Plugin"
+	FunctionCategoryFakeData    = "Fake Data Generation"
 )
 
 // FunctionSpec describes a Bloblang function.
diff --git a/internal/docs/bloblang.go b/internal/docs/bloblang.go
index 9646c33d65..96cdf0743e 100644
--- a/internal/docs/bloblang.go
+++ b/internal/docs/bloblang.go
@@ -194,6 +194,7 @@ func BloblangFunctionsMarkdown() ([]byte, error) {
 		query.FunctionCategoryGeneral,
 		query.FunctionCategoryMessage,
 		query.FunctionCategoryEnvironment,
+		query.FunctionCategoryFakeData,
 		query.FunctionCategoryDeprecated,
 	} {
 		functions := functionCategory{
diff --git a/internal/impl/lang/bloblang.go b/internal/impl/lang/bloblang.go
index 24f13b5c1c..8abdabab56 100644
--- a/internal/impl/lang/bloblang.go
+++ b/internal/impl/lang/bloblang.go
@@ -1,9 +1,13 @@
 package lang
 
 import (
-	"github.com/gosimple/slug"
+	"fmt"
+	"strings"
 
+	"github.com/benthosdev/benthos/v4/internal/bloblang/query"
 	"github.com/benthosdev/benthos/v4/public/bloblang"
+	"github.com/bxcodec/faker/v3"
+	"github.com/gosimple/slug"
 )
 
 func init() {
@@ -42,4 +46,160 @@ func init() {
 	); err != nil {
 		panic(err)
 	}
+
+	fakerSpec := bloblang.NewPluginSpec().
+		Experimental().
+		Category(query.FunctionCategoryFakeData).
+		Description("Takes in a string that maps to a [faker](https://github.com/bxcodec/faker) function and returns the result from that faker function. "+
+			"Returns an error if the given string doesn't match a supported faker function. Supported functions: `latitude`, `longitude`, `unix_time`, "+
+			"`date`, `time_string`, `month_name`, `year_string`, `day_of_week`, `day_of_month`, `timestamp`, `century`, `timezone`, `time_period`, "+
+			"`email`, `mac_address`, `domain_name`, `url`, `username`, `ipv4`, `ipv6`, `password`, `jwt`, `word`, `sentence`, `paragraph`, "+
+			"`cc_type`, `cc_number`, `currency`, `amount_with_currency`, `title_male`, `title_female`, `first_name`, `first_name_male`, "+
+			"`first_name_female`, `last_name`, `name`, `gender`, `chinese_first_name`, `chinese_last_name`, `chinese_name`, `phone_number`, "+
+			"`toll_free_phone_number`, `e164_phone_number`, `uuid_hyphenated`, `uuid_digit`. Refer to the [faker](https://github.com/bxcodec/faker) docs "+
+			"for details on these functions.").
+		Param(bloblang.NewStringParam("function").Description("The name of the function to use to generate the value.").Default("")).
+		Example("Generates a time in the format 00:00:00",
+			`root.time = fake("time_string")`).
+		Example("Generates a string in email address format.",
+			`root.email = fake("email")`).
+		Example("Generates a JWT token",
+			`root.jwt = fake("jwt")`).
+		Example("Generates a hypenated UUID",
+			`root.uuid = fake("uuid_hyphenated")`)
+
+	if err := bloblang.RegisterFunctionV2(
+		"fake", fakerSpec,
+		func(args *bloblang.ParsedParams) (bloblang.Function, error) {
+			functionKey, err := args.GetString("function")
+			if err != nil {
+				return nil, err
+			}
+
+			return func() (interface{}, error) {
+				return GetFakeValue(functionKey)
+			}, nil
+		},
+	); err != nil {
+		panic(err)
+	}
+}
+
+// GetFakeValue returns fake data generated by the faker function corresponding to the input string
+func GetFakeValue(function string) (interface{}, error) {
+	switch strings.ToLower(function) {
+
+	// Location functions
+	case "latitude":
+		return faker.Latitude(), nil
+	case "longitude":
+		return faker.Longitude(), nil
+
+	// Date time functions
+	case "unix_time":
+		return faker.UnixTime(), nil
+	case "date":
+		return faker.Date(), nil
+	case "time_string":
+		return faker.TimeString(), nil
+	case "month_name":
+		return faker.MonthName(), nil
+	case "year_string":
+		return faker.YearString(), nil
+	case "day_of_week":
+		return faker.DayOfWeek(), nil
+	case "day_of_month":
+		return faker.DayOfMonth(), nil
+	case "timestamp":
+		return faker.Timestamp(), nil
+	case "century":
+		return faker.Century(), nil
+	case "timezone":
+		return faker.Timezone(), nil
+	case "time_period":
+		return faker.Timeperiod(), nil
+
+	// Internet functions
+	case "email":
+		return faker.Email(), nil
+	case "mac_address":
+		return faker.MacAddress(), nil
+	case "domain_name":
+		return faker.DomainName(), nil
+	case "url":
+		return faker.URL(), nil
+	case "username":
+		return faker.Username(), nil
+	case "ipv4":
+		return faker.IPv4(), nil
+	case "ipv6":
+		return faker.IPv6(), nil
+	case "password":
+		return faker.Password(), nil
+	case "jwt":
+		return faker.Jwt(), nil
+
+	// Words and sentences functions
+	case "word":
+		return faker.Word(), nil
+	case "sentence":
+		return faker.Sentence(), nil
+	case "paragraph":
+		return faker.Paragraph(), nil
+
+	// Payment
+	case "cc_type":
+		return faker.CCType(), nil
+	case "cc_number":
+		return faker.CCNumber(), nil
+	case "currency":
+		return faker.Currency(), nil
+	case "amount_with_currency":
+		return faker.AmountWithCurrency(), nil
+
+	// Person functions
+	case "title_male":
+		return faker.TitleMale(), nil
+	case "title_female":
+		return faker.TitleFemale(), nil
+	case "first_name":
+		return faker.FirstName(), nil
+	case "first_name_male":
+		return faker.FirstNameMale(), nil
+	case "first_name_female":
+		return faker.FirstNameFemale(), nil
+	case "last_name":
+		return faker.LastName(), nil
+	case "name":
+		return faker.Name(), nil
+	case "gender":
+		return faker.Gender(), nil
+	case "chinese_first_name":
+		return faker.ChineseFirstName(), nil
+	case "chinese_last_name":
+		return faker.ChineseLastName(), nil
+	case "chinese_name":
+		return faker.ChineseName(), nil
+
+	// Phone functions
+	case "phone_number":
+		return faker.Phonenumber(), nil
+	case "toll_free_phone_number":
+		return faker.TollFreePhoneNumber(), nil
+	case "e164_phone_number":
+		return faker.E164PhoneNumber(), nil
+
+	// UUID functions
+	case "uuid_hyphenated":
+		return faker.UUIDHyphenated(), nil
+	case "uuid_digit":
+		return faker.UUIDDigit(), nil
+
+	case "":
+		var str string
+		err := faker.FakeData(&str)
+		return str, err
+	}
+
+	return "", fmt.Errorf("invalid faker function: %s", function)
 }
diff --git a/website/docs/guides/bloblang/functions.md b/website/docs/guides/bloblang/functions.md
index 5d281ee9d2..b1afdbe652 100644
--- a/website/docs/guides/bloblang/functions.md
+++ b/website/docs/guides/bloblang/functions.md
@@ -413,6 +413,46 @@ Returns the current unix timestamp in nanoseconds.
 root.received_at = timestamp_unix_nano()
 ```
 
+## Fake Data Generation
+
+### `fake`
+
+:::caution EXPERIMENTAL
+This function is experimental and therefore breaking changes could be made to it outside of major version releases.
+:::
+Takes in a string that maps to a [faker](https://github.com/bxcodec/faker) function and returns the result from that faker function. Returns an error if the given string doesn't match a supported faker function. Supported functions: `latitude`, `longitude`, `unix_time`, `date`, `time_string`, `month_name`, `year_string`, `day_of_week`, `day_of_month`, `timestamp`, `century`, `timezone`, `time_period`, `email`, `mac_address`, `domain_name`, `url`, `username`, `ipv4`, `ipv6`, `password`, `jwt`, `word`, `sentence`, `paragraph`, `cc_type`, `cc_number`, `currency`, `amount_with_currency`, `title_male`, `title_female`, `first_name`, `first_name_male`, `first_name_female`, `last_name`, `name`, `gender`, `chinese_first_name`, `chinese_last_name`, `chinese_name`, `phone_number`, `toll_free_phone_number`, `e164_phone_number`, `uuid_hyphenated`, `uuid_digit`. Refer to the [faker](https://github.com/bxcodec/faker) docs for details on these functions.
+
+#### Parameters
+
+**`function`** &lt;string, default `""`&gt; The name of the function to use to generate the value.  
+
+#### Examples
+
+
+Generates a time in the format 00:00:00
+
+```coffee
+root.time = fake("time_string")
+```
+
+Generates a string in email address format.
+
+```coffee
+root.email = fake("email")
+```
+
+Generates a JWT token
+
+```coffee
+root.jwt = fake("jwt")
+```
+
+Generates a hypenated UUID
+
+```coffee
+root.uuid = fake("uuid_hyphenated")
+```
+
 [error_handling]: /docs/configuration/error_handling
 [field_paths]: /docs/configuration/field_paths
 [meta_proc]: /docs/components/processors/metadata


Test patch:
diff --git a/internal/impl/lang/bloblang_test.go b/internal/impl/lang/bloblang_test.go
new file mode 100644
index 0000000000..3a53878c39
--- /dev/null
+++ b/internal/impl/lang/bloblang_test.go
@@ -0,0 +1,50 @@
+package lang
+
+import (
+	"testing"
+
+	"github.com/benthosdev/benthos/v4/internal/bloblang/query"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestFakeFunction_Invalid(t *testing.T) {
+	e, err := query.InitFunctionHelper("fake", "foo")
+	require.Nil(t, err)
+
+	res, err := e.Exec(query.FunctionContext{})
+	require.Error(t, err, "invalid faker function: foo")
+	assert.Empty(t, res)
+}
+
+func TestFieldsFromNode(t *testing.T) {
+	tests := []struct {
+		name     string
+		function string
+	}{
+		{
+			name:     "default",
+			function: "",
+		},
+		{
+			name:     "email function",
+			function: "email",
+		},
+		{
+			name:     "phone number function",
+			function: "phone_number",
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			e, err := query.InitFunctionHelper("fake", test.function)
+			require.Nil(t, err)
+
+			res, err := e.Exec(query.FunctionContext{})
+			require.NoError(t, err)
+
+			assert.NotEmpty(t, res)
+		})
+	}
+}