Repository: xataio/pgstream
Pull request #164 (2025-03-11 10:13:05)

Add ns email transformer
Add `EmailTransformer` which uses neosync's email transformer

github-actions[bot]: ### Merging this branch changes the coverage (1 decrease, 1 increase)

| Impacted Packages | Coverage Δ | :robot: |
|-------------------|------------|---------|
| github.com/xataio/pgstream/pkg/transformers | 100.00% (ø) |  |
| github.com/xataio/pgstream/pkg/transformers/builder | 8.33% (**-1.92%**) | :thumbsdown: |
| github.com/xataio/pgstream/pkg/transformers/neosync | 92.00% (**+2.00%**) | :thumbsup: |

---

<details>

<summary>Coverage by file</summary>

### Changed files (no unit tests)

| Changed File | Coverage Δ | Total | Covered | Missed | :robot: |
|--------------|------------|-------|---------|--------|---------|
| github.com/xataio/pgstream/pkg/transformers/builder/transformer_builder.go | 8.33% (**-1.92%**) | 48 (+9) | 4 | 44 (+9) | :thumbsdown: |
| github.com/xataio/pgstream/pkg/transformers/neosync/neosync_email_transformer.go | 92.00% (**+92.00%**) | 25 (+25) | 23 (+23) | 2 (+2) | :star2: |
| github.com/xataio/pgstream/pkg/transformers/transformer.go | 100.00% (ø) | 7 | 7 | 0 |  |

_Please note that the "Total", "Covered", and "Missed" counts above refer to ***code statements*** instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code._

### Changed unit test files

- github.com/xataio/pgstream/pkg/transformers/neosync/neosync_email_transformer_test.go

</details>

github-actions[bot]: ### Merging this branch changes the coverage (1 decrease, 1 increase)

| Impacted Packages | Coverage Δ | :robot: |
|-------------------|------------|---------|
| github.com/xataio/pgstream/pkg/transformers | 100.00% (ø) |  |
| github.com/xataio/pgstream/pkg/transformers/builder | 8.33% (**-1.92%**) | :thumbsdown: |
| github.com/xataio/pgstream/pkg/transformers/neosync | 92.00% (**+2.00%**) | :thumbsup: |

---

<details>

<summary>Coverage by file</summary>

### Changed files (no unit tests)

| Changed File | Coverage Δ | Total | Covered | Missed | :robot: |
|--------------|------------|-------|---------|--------|---------|
| github.com/xataio/pgstream/pkg/transformers/builder/transformer_builder.go | 8.33% (**-1.92%**) | 48 (+9) | 4 | 44 (+9) | :thumbsdown: |
| github.com/xataio/pgstream/pkg/transformers/neosync/neosync_email_transformer.go | 92.00% (**+92.00%**) | 25 (+25) | 23 (+23) | 2 (+2) | :star2: |
| github.com/xataio/pgstream/pkg/transformers/transformer.go | 100.00% (ø) | 7 | 7 | 0 |  |

_Please note that the "Total", "Covered", and "Missed" counts above refer to ***code statements*** instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code._

### Changed unit test files

- github.com/xataio/pgstream/pkg/transformers/neosync/neosync_email_transformer_test.go

</details>

github-actions[bot]: ### Merging this branch will **decrease** overall coverage

| Impacted Packages | Coverage Δ | :robot: |
|-------------------|------------|---------|
| github.com/xataio/pgstream/pkg/transformers | 100.00% (ø) |  |
| github.com/xataio/pgstream/pkg/transformers/builder | 8.33% (**-0.56%**) | :thumbsdown: |
| github.com/xataio/pgstream/pkg/transformers/neosync | 89.47% (**-0.53%**) | :thumbsdown: |

---

<details>

<summary>Coverage by file</summary>

### Changed files (no unit tests)

| Changed File | Coverage Δ | Total | Covered | Missed | :robot: |
|--------------|------------|-------|---------|--------|---------|
| github.com/xataio/pgstream/pkg/transformers/builder/transformer_builder.go | 8.33% (**-0.56%**) | 48 (+3) | 4 | 44 (+3) | :thumbsdown: |
| github.com/xataio/pgstream/pkg/transformers/neosync/neosync_email_transformer.go | 86.67% (**+86.67%**) | 45 (+45) | 39 (+39) | 6 (+6) | :star2: |
| github.com/xataio/pgstream/pkg/transformers/transformer.go | 100.00% (ø) | 7 | 7 | 0 |  |

_Please note that the "Total", "Covered", and "Missed" counts above refer to ***code statements*** instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code._

### Changed unit test files

- github.com/xataio/pgstream/pkg/transformers/neosync/neosync_email_transformer_test.go

</details>

github-actions[bot]: ### Merging this branch changes the coverage (1 decrease, 1 increase)

| Impacted Packages | Coverage Δ | :robot: |
|-------------------|------------|---------|
| github.com/xataio/pgstream/pkg/transformers | 100.00% (ø) |  |
| github.com/xataio/pgstream/pkg/transformers/builder | 8.33% (**-0.56%**) | :thumbsdown: |
| github.com/xataio/pgstream/pkg/transformers/neosync | 92.22% (**+2.22%**) | :thumbsup: |

---

<details>

<summary>Coverage by file</summary>

### Changed files (no unit tests)

| Changed File | Coverage Δ | Total | Covered | Missed | :robot: |
|--------------|------------|-------|---------|--------|---------|
| github.com/xataio/pgstream/pkg/transformers/builder/transformer_builder.go | 8.33% (**-0.56%**) | 48 (+3) | 4 | 44 (+3) | :thumbsdown: |
| github.com/xataio/pgstream/pkg/transformers/neosync/neosync_email_transformer.go | 92.50% (**+92.50%**) | 40 (+40) | 37 (+37) | 3 (+3) | :star2: |
| github.com/xataio/pgstream/pkg/transformers/transformer.go | 100.00% (ø) | 7 | 7 | 0 |  |

_Please note that the "Total", "Covered", and "Missed" counts above refer to ***code statements*** instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code._

### Changed unit test files

- github.com/xataio/pgstream/pkg/transformers/neosync/neosync_email_transformer_test.go

</details>

github-actions[bot]: ### Merging this branch changes the coverage (1 decrease, 1 increase)

| Impacted Packages | Coverage Δ | :robot: |
|-------------------|------------|---------|
| github.com/xataio/pgstream/pkg/transformers | 100.00% (ø) |  |
| github.com/xataio/pgstream/pkg/transformers/builder | 8.33% (**-0.56%**) | :thumbsdown: |
| github.com/xataio/pgstream/pkg/transformers/neosync | 93.90% (**+3.90%**) | :thumbsup: |

---

<details>

<summary>Coverage by file</summary>

### Changed files (no unit tests)

| Changed File | Coverage Δ | Total | Covered | Missed | :robot: |
|--------------|------------|-------|---------|--------|---------|
| github.com/xataio/pgstream/pkg/transformers/builder/transformer_builder.go | 8.33% (**-0.56%**) | 48 (+3) | 4 | 44 (+3) | :thumbsdown: |
| github.com/xataio/pgstream/pkg/transformers/neosync/neosync_email_transformer.go | 96.88% (**+96.88%**) | 32 (+32) | 31 (+31) | 1 (+1) | :star2: |
| github.com/xataio/pgstream/pkg/transformers/transformer.go | 100.00% (ø) | 7 | 7 | 0 |  |

_Please note that the "Total", "Covered", and "Missed" counts above refer to ***code statements*** instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code._

### Changed unit test files

- github.com/xataio/pgstream/pkg/transformers/neosync/neosync_email_transformer_test.go

</details>

github-actions[bot]: ### Merging this branch changes the coverage (1 decrease, 1 increase)

| Impacted Packages | Coverage Δ | :robot: |
|-------------------|------------|---------|
| github.com/xataio/pgstream/pkg/transformers | 100.00% (ø) |  |
| github.com/xataio/pgstream/pkg/transformers/builder | 8.33% (**-0.56%**) | :thumbsdown: |
| github.com/xataio/pgstream/pkg/transformers/neosync | 93.90% (**+3.90%**) | :thumbsup: |

---

<details>

<summary>Coverage by file</summary>

### Changed files (no unit tests)

| Changed File | Coverage Δ | Total | Covered | Missed | :robot: |
|--------------|------------|-------|---------|--------|---------|
| github.com/xataio/pgstream/pkg/transformers/builder/transformer_builder.go | 8.33% (**-0.56%**) | 48 (+3) | 4 | 44 (+3) | :thumbsdown: |
| github.com/xataio/pgstream/pkg/transformers/neosync/neosync_email_transformer.go | 96.88% (**+96.88%**) | 32 (+32) | 31 (+31) | 1 (+1) | :star2: |
| github.com/xataio/pgstream/pkg/transformers/transformer.go | 100.00% (ø) | 7 | 7 | 0 |  |

_Please note that the "Total", "Covered", and "Missed" counts above refer to ***code statements*** instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code._

### Changed unit test files

- github.com/xataio/pgstream/pkg/transformers/neosync/neosync_email_transformer_test.go

</details>

github-actions[bot]: ### Merging this branch changes the coverage (1 decrease, 1 increase)

| Impacted Packages | Coverage Δ | :robot: |
|-------------------|------------|---------|
| github.com/xataio/pgstream/pkg/transformers | 100.00% (ø) |  |
| github.com/xataio/pgstream/pkg/transformers/builder | 8.33% (**-0.56%**) | :thumbsdown: |
| github.com/xataio/pgstream/pkg/transformers/neosync | 93.90% (**+3.90%**) | :thumbsup: |

---

<details>

<summary>Coverage by file</summary>

### Changed files (no unit tests)

| Changed File | Coverage Δ | Total | Covered | Missed | :robot: |
|--------------|------------|-------|---------|--------|---------|
| github.com/xataio/pgstream/pkg/transformers/builder/transformer_builder.go | 8.33% (**-0.56%**) | 48 (+3) | 4 | 44 (+3) | :thumbsdown: |
| github.com/xataio/pgstream/pkg/transformers/neosync/neosync_email_transformer.go | 96.88% (**+96.88%**) | 32 (+32) | 31 (+31) | 1 (+1) | :star2: |
| github.com/xataio/pgstream/pkg/transformers/transformer.go | 100.00% (ø) | 7 | 7 | 0 |  |

_Please note that the "Total", "Covered", and "Missed" counts above refer to ***code statements*** instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code._

### Changed unit test files

- github.com/xataio/pgstream/pkg/transformers/neosync/neosync_email_transformer_test.go

</details>

github-actions[bot]: ### Merging this branch changes the coverage (1 decrease, 1 increase)

| Impacted Packages | Coverage Δ | :robot: |
|-------------------|------------|---------|
| github.com/xataio/pgstream/pkg/transformers | 100.00% (ø) |  |
| github.com/xataio/pgstream/pkg/transformers/builder | 8.33% (**-0.56%**) | :thumbsdown: |
| github.com/xataio/pgstream/pkg/transformers/neosync | 94.05% (**+4.05%**) | :thumbsup: |

---

<details>

<summary>Coverage by file</summary>

### Changed files (no unit tests)

| Changed File | Coverage Δ | Total | Covered | Missed | :robot: |
|--------------|------------|-------|---------|--------|---------|
| github.com/xataio/pgstream/pkg/transformers/builder/transformer_builder.go | 8.33% (**-0.56%**) | 48 (+3) | 4 | 44 (+3) | :thumbsdown: |
| github.com/xataio/pgstream/pkg/transformers/neosync/neosync_email_transformer.go | 96.55% (**+96.55%**) | 29 (+29) | 28 (+28) | 1 (+1) | :star2: |
| github.com/xataio/pgstream/pkg/transformers/neosync/neosync_transformer.go | 92.31% (**+6.59%**) | 26 (+5) | 24 (+6) | 2 (-1) | :thumbsup: |
| github.com/xataio/pgstream/pkg/transformers/transformer.go | 100.00% (ø) | 7 | 7 | 0 |  |

_Please note that the "Total", "Covered", and "Missed" counts above refer to ***code statements*** instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code._

### Changed unit test files

- github.com/xataio/pgstream/pkg/transformers/neosync/neosync_email_transformer_test.go

</details>

github-actions[bot]: ### Merging this branch changes the coverage (1 decrease, 1 increase)

| Impacted Packages | Coverage Δ | :robot: |
|-------------------|------------|---------|
| github.com/xataio/pgstream/pkg/transformers | 100.00% (ø) |  |
| github.com/xataio/pgstream/pkg/transformers/builder | 8.33% (**-0.56%**) | :thumbsdown: |
| github.com/xataio/pgstream/pkg/transformers/neosync | 93.98% (**+3.98%**) | :thumbsup: |

---

<details>

<summary>Coverage by file</summary>

### Changed files (no unit tests)

| Changed File | Coverage Δ | Total | Covered | Missed | :robot: |
|--------------|------------|-------|---------|--------|---------|
| github.com/xataio/pgstream/pkg/transformers/builder/transformer_builder.go | 8.33% (**-0.56%**) | 48 (+3) | 4 | 44 (+3) | :thumbsdown: |
| github.com/xataio/pgstream/pkg/transformers/neosync/neosync_email_transformer.go | 96.55% (**+96.55%**) | 29 (+29) | 28 (+28) | 1 (+1) | :star2: |
| github.com/xataio/pgstream/pkg/transformers/neosync/neosync_transformer.go | 92.00% (**+6.29%**) | 25 (+4) | 23 (+5) | 2 (-1) | :thumbsup: |
| github.com/xataio/pgstream/pkg/transformers/transformer.go | 100.00% (ø) | 7 | 7 | 0 |  |

_Please note that the "Total", "Covered", and "Missed" counts above refer to ***code statements*** instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code._

### Changed unit test files

- github.com/xataio/pgstream/pkg/transformers/neosync/neosync_email_transformer_test.go

</details>

github-actions[bot]: ### Merging this branch changes the coverage (1 decrease, 1 increase)

| Impacted Packages | Coverage Δ | :robot: |
|-------------------|------------|---------|
| github.com/xataio/pgstream/pkg/transformers | 100.00% (ø) |  |
| github.com/xataio/pgstream/pkg/transformers/builder | 8.33% (**-0.56%**) | :thumbsdown: |
| github.com/xataio/pgstream/pkg/transformers/neosync | 93.98% (**+3.98%**) | :thumbsup: |

---

<details>

<summary>Coverage by file</summary>

### Changed files (no unit tests)

| Changed File | Coverage Δ | Total | Covered | Missed | :robot: |
|--------------|------------|-------|---------|--------|---------|
| github.com/xataio/pgstream/pkg/transformers/builder/transformer_builder.go | 8.33% (**-0.56%**) | 48 (+3) | 4 | 44 (+3) | :thumbsdown: |
| github.com/xataio/pgstream/pkg/transformers/neosync/neosync_email_transformer.go | 96.55% (**+96.55%**) | 29 (+29) | 28 (+28) | 1 (+1) | :star2: |
| github.com/xataio/pgstream/pkg/transformers/neosync/neosync_transformer.go | 92.00% (**+6.29%**) | 25 (+4) | 23 (+5) | 2 (-1) | :thumbsup: |
| github.com/xataio/pgstream/pkg/transformers/transformer.go | 100.00% (ø) | 7 | 7 | 0 |  |

_Please note that the "Total", "Covered", and "Missed" counts above refer to ***code statements*** instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code._

### Changed unit test files

- github.com/xataio/pgstream/pkg/transformers/neosync/neosync_email_transformer_test.go

</details>


Patch:
diff --git a/pkg/transformers/builder/transformer_builder.go b/pkg/transformers/builder/transformer_builder.go
index 44c35f9..a5bfeb3 100644
--- a/pkg/transformers/builder/transformer_builder.go
+++ b/pkg/transformers/builder/transformer_builder.go
@@ -20,6 +20,8 @@ func New(cfg *transformers.Config) (transformers.Transformer, error) {
 		return neosync.NewFirstNameTransformer(cfg.Parameters)
 	case transformers.GreenmaskFirstName:
 		return greenmask.NewFirstNameTransformer(cfg.Generator, cfg.Parameters)
+	case transformers.NeosyncEmail:
+		return neosync.NewEmailTransformer(cfg.Parameters)
 	case transformers.GreenmaskInteger:
 		return greenmask.NewIntegerTransformer(cfg.Generator, cfg.Parameters)
 	case transformers.GreenmaskFloat:
diff --git a/pkg/transformers/neosync/neosync_email_transformer.go b/pkg/transformers/neosync/neosync_email_transformer.go
new file mode 100644
index 0000000..4192f77
--- /dev/null
+++ b/pkg/transformers/neosync/neosync_email_transformer.go
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: Apache-2.0
+
+package neosync
+
+import (
+	"errors"
+	"fmt"
+	"slices"
+
+	neosynctransformers "github.com/nucleuscloud/neosync/worker/pkg/benthos/transformers"
+	"github.com/xataio/pgstream/pkg/transformers"
+)
+
+type EmailTransformer struct {
+	*transformer[string]
+}
+
+var (
+	errInvalidEmailType          = errors.New("email_type must be one of 'uuidv4', 'fullname' or 'any'")
+	errInvalidInvalidEmailAction = errors.New("invalid_email_action must be one of 'reject', 'passthrough', 'null' or 'generate'")
+
+	validEmailTypes = []string{
+		neosynctransformers.GenerateEmailType_UuidV4.String(),
+		neosynctransformers.GenerateEmailType_FullName.String(),
+		neosynctransformers.GenerateEmailType_Any.String(),
+	}
+	validInvalidEmailActions = []string{
+		neosynctransformers.InvalidEmailAction_Reject.String(),
+		neosynctransformers.InvalidEmailAction_Passthrough.String(),
+		neosynctransformers.InvalidEmailAction_Null.String(),
+		neosynctransformers.InvalidEmailAction_Generate.String(),
+	}
+)
+
+func NewEmailTransformer(params transformers.Parameters) (*EmailTransformer, error) {
+	preserveLength, err := findParameter[bool](params, "preserve_length")
+	if err != nil {
+		return nil, fmt.Errorf("neosync_email: preserve_length must be a boolean: %w", err)
+	}
+
+	preserveDomain, err := findParameter[bool](params, "preserve_domain")
+	if err != nil {
+		return nil, fmt.Errorf("neosync_email: preserve_domain must be a boolean: %w", err)
+	}
+
+	excludedDomains, err := findParameter[[]string](params, "excluded_domains")
+	if err != nil {
+		return nil, fmt.Errorf("neosync_email: excluded_domains must be type of []string: %w", err)
+	}
+
+	maxLength, err := findParameter[int](params, "max_length")
+	if err != nil {
+		return nil, fmt.Errorf("neosync_email: max_length must be an integer: %w", err)
+	}
+
+	seed, err := findParameter[int](params, "seed")
+	if err != nil {
+		return nil, fmt.Errorf("neosync_email: seed must be an integer: %w", err)
+	}
+
+	emailType, err := findParameter[string](params, "email_type")
+	if err != nil {
+		return nil, fmt.Errorf("neosync_email: email_type must be a string: %w", err)
+	}
+	if emailType != nil && !slices.Contains(validEmailTypes, *emailType) {
+		return nil, errInvalidEmailType
+	}
+
+	invalidEmailAction, err := findParameter[string](params, "invalid_email_action")
+	if err != nil {
+		return nil, fmt.Errorf("neosync_email: invalid_email_action must be a string: %w", err)
+	}
+	if invalidEmailAction != nil && !slices.Contains(validInvalidEmailActions, *invalidEmailAction) {
+		return nil, errInvalidInvalidEmailAction
+	}
+
+	opts, err := neosynctransformers.NewTransformEmailOpts(preserveLength, preserveDomain, toAnyPtr(excludedDomains), toInt64Ptr(maxLength), toInt64Ptr(seed), emailType, invalidEmailAction)
+	if err != nil {
+		return nil, err
+	}
+
+	return &EmailTransformer{
+		transformer: New[string](neosynctransformers.NewTransformEmail(), opts),
+	}, nil
+}
diff --git a/pkg/transformers/neosync/neosync_transformer.go b/pkg/transformers/neosync/neosync_transformer.go
index 0a8cc5d..b400475 100644
--- a/pkg/transformers/neosync/neosync_transformer.go
+++ b/pkg/transformers/neosync/neosync_transformer.go
@@ -61,3 +61,12 @@ func toInt64Ptr(i *int) *int64 {
 	i64 := int64(*i)
 	return &i64
 }
+
+func toAnyPtr(strArray *[]string) *any {
+	if strArray == nil {
+		return nil
+	}
+
+	strArrayAny := any(*strArray)
+	return &strArrayAny
+}
diff --git a/pkg/transformers/transformer.go b/pkg/transformers/transformer.go
index 0fee9d1..0d5cc63 100644
--- a/pkg/transformers/transformer.go
+++ b/pkg/transformers/transformer.go
@@ -24,6 +24,7 @@ const (
 	GreenmaskString        TransformerType = "greenmask_string"
 	NeosyncFirstName       TransformerType = "neosync_firstname"
 	GreenmaskFirstName     TransformerType = "greenmask_firstname"
+	NeosyncEmail           TransformerType = "neosync_email"
 	GreenmaskInteger       TransformerType = "greenmask_integer"
 	GreenmaskFloat         TransformerType = "greenmask_float"
 	GreenmaskUUID          TransformerType = "greenmask_uuid"


Test patch:
diff --git a/pkg/transformers/neosync/neosync_email_transformer_test.go b/pkg/transformers/neosync/neosync_email_transformer_test.go
new file mode 100644
index 0000000..f8617b0
--- /dev/null
+++ b/pkg/transformers/neosync/neosync_email_transformer_test.go
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: Apache-2.0
+
+package neosync
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"github.com/xataio/pgstream/pkg/transformers"
+)
+
+func TestNewEmailTransformer(t *testing.T) {
+	t.Parallel()
+	tests := []struct {
+		name    string
+		params  transformers.Parameters
+		wantErr error
+	}{
+		{
+			name:    "ok - valid default parameters",
+			params:  transformers.Parameters{},
+			wantErr: nil,
+		},
+		{
+			name: "ok - valid custom parameters",
+			params: transformers.Parameters{
+				"email_type":           "fullname",
+				"invalid_email_action": "generate",
+				"excluded_domains":     []string{"example.com", "example.org"},
+				"max_length":           10,
+				"preserve_domain":      true,
+				"preserve_length":      true,
+				"seed":                 0,
+			},
+			wantErr: nil,
+		},
+		{
+			name: "error - invalid preserve_length",
+			params: transformers.Parameters{
+				"preserve_length": 1,
+			},
+			wantErr: transformers.ErrInvalidParameters,
+		},
+		{
+			name: "error - invalid preserve_domain",
+			params: transformers.Parameters{
+				"preserve_domain": 1,
+			},
+			wantErr: transformers.ErrInvalidParameters,
+		},
+		{
+			name: "error - invalid max_length",
+			params: transformers.Parameters{
+				"max_length": "1",
+			},
+			wantErr: transformers.ErrInvalidParameters,
+		},
+		{
+			name: "error - invalid seed",
+			params: transformers.Parameters{
+				"seed": "1",
+			},
+			wantErr: transformers.ErrInvalidParameters,
+		},
+		{
+			name: "error - invalid excluded_domains, []any",
+			params: transformers.Parameters{
+				"excluded_domains": []any{"example.com", 3},
+			},
+			wantErr: transformers.ErrInvalidParameters,
+		},
+		{
+			name: "error - invalid email_type",
+			params: transformers.Parameters{
+				"email_type": 1,
+			},
+			wantErr: transformers.ErrInvalidParameters,
+		},
+		{
+			name: "error - invalid email_type value",
+			params: transformers.Parameters{
+				"email_type": "invalid",
+			},
+			wantErr: errInvalidEmailType,
+		},
+		{
+			name: "error - invalid invalid_email_action",
+			params: transformers.Parameters{
+				"invalid_email_action": 1,
+			},
+			wantErr: transformers.ErrInvalidParameters,
+		},
+		{
+			name: "error - invalid invalid_email_action value",
+			params: transformers.Parameters{
+				"invalid_email_action": "invalid",
+			},
+			wantErr: errInvalidInvalidEmailAction,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+			transformer, err := NewEmailTransformer(tt.params)
+			require.ErrorIs(t, err, tt.wantErr)
+			if err != nil {
+				return
+			}
+			require.NotNil(t, transformer)
+		})
+	}
+}
+
+func TestEmailTransformer_Transform(t *testing.T) {
+	t.Parallel()
+	tests := []struct {
+		name               string
+		input              string
+		emailType          string
+		invalidEmailAction string
+		excludedDomains    []string
+		maxLength          int
+		preserveDomain     bool
+		preserveLength     bool
+		seed               int
+
+		wantEmail string
+		wantErr   error
+	}{
+		{
+			name:               "ok - valid custom parameters",
+			input:              "myname@lastname.com",
+			emailType:          "fullname",
+			invalidEmailAction: "generate",
+			excludedDomains:    []string{"example.com", "example.org"},
+			maxLength:          0,
+			preserveDomain:     false,
+			preserveLength:     false,
+			seed:               0,
+
+			wantEmail: "machadopasqui@donga.com",
+			wantErr:   nil,
+		},
+		{
+			name:               "ok - valid custom parameters, preserve length",
+			input:              "myname@lastname.com",
+			emailType:          "fullname",
+			invalidEmailAction: "generate",
+			excludedDomains:    []string{"example.com", "example.org"},
+			maxLength:          20,
+			preserveDomain:     false,
+			preserveLength:     true,
+			seed:               0,
+
+			wantEmail: "malisuaul@baike.com",
+			wantErr:   nil,
+		},
+		{
+			name:               "ok - valid custom parameters, preserve domain",
+			input:              "myname@lastname.com",
+			emailType:          "uuidv4",
+			invalidEmailAction: "passthrough",
+			maxLength:          17,
+			preserveDomain:     true,
+			preserveLength:     false,
+			seed:               0,
+
+			wantEmail: "",
+			wantErr:   nil,
+		},
+	}
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			params := transformers.Parameters{
+				"email_type":           tc.emailType,
+				"invalid_email_action": tc.invalidEmailAction,
+				"excluded_domains":     tc.excludedDomains,
+				"max_length":           tc.maxLength,
+				"preserve_domain":      tc.preserveDomain,
+				"preserve_length":      tc.preserveLength,
+				"seed":                 tc.seed,
+			}
+			transformer, err := NewEmailTransformer(params)
+			require.NoError(t, err)
+			got, err := transformer.Transform(tc.input)
+			require.ErrorIs(t, err, tc.wantErr)
+			require.NotNil(t, got)
+			val, ok := got.(string)
+			require.True(t, ok)
+			require.NotEmpty(t, val)
+
+			_, domainExpected, _ := strings.Cut(tc.input, "@")
+			_, domainGot, found := strings.Cut(val, "@")
+			require.True(t, found)
+			if tc.preserveDomain {
+				require.Equal(t, domainExpected, domainGot)
+			}
+
+			if tc.excludedDomains != nil {
+				require.NotContains(t, tc.excludedDomains, domainGot)
+			}
+
+			if tc.preserveLength {
+				require.Equal(t, len(tc.input), len(val))
+			}
+
+			if tc.maxLength != 0 {
+				require.LessOrEqual(t, len(val), tc.maxLength)
+			}
+
+			if tc.wantEmail != "" {
+				require.Equal(t, tc.wantEmail, val)
+			}
+		})
+	}
+}