Repository: xataio/pgstream
Pull request #333 (2025-05-12 10:18:59)

Add neosync fullname transformer
Adding fullname transformer from neosync


Patch:
diff --git a/docs/README.md b/docs/README.md
index e640fdc..4b84980 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -971,6 +971,36 @@ transformations:
             preserve_length: true
 ```
 
+</details>
+ <details>
+  <summary>neosync_fullname</summary>
+
+**Description:** Generates anonymized full names while optionally preserving length.
+
+| Supported PostgreSQL types          |
+| ----------------------------------- |
+| `text`, `varchar`, `char`, `bpchar` |
+
+| Parameter       | Type | Default | Required |
+| --------------- | ---- | ------- | -------- |
+| preserve_length | bool | false   | No       |
+| max_length      | int  | 100     | No       |
+| seed            | int  | Rand    | No       |
+
+**Example Configuration:**
+
+```yaml
+transformations:
+  table_transformers:
+    - schema: public
+      table: users
+      column_transformers:
+        full_name:
+          name: neosync_fullname
+          parameters:
+            preserve_length: true
+```
+
 </details>
  <details>
   <summary>neosync_string</summary>
diff --git a/pkg/transformers/builder/transformer_builder.go b/pkg/transformers/builder/transformer_builder.go
index 51213bb..c61279f 100644
--- a/pkg/transformers/builder/transformer_builder.go
+++ b/pkg/transformers/builder/transformer_builder.go
@@ -70,6 +70,8 @@ func (b *TransformerBuilder) New(cfg *transformers.Config) (t transformers.Trans
 		return neosync.NewFirstNameTransformer(cfg.Parameters)
 	case transformers.NeosyncLastName:
 		return neosync.NewLastNameTransformer(cfg.Parameters)
+	case transformers.NeosyncFullName:
+		return neosync.NewFullNameTransformer(cfg.Parameters)
 	case transformers.NeosyncEmail:
 		return neosync.NewEmailTransformer(cfg.Parameters)
 	case transformers.PhoneNumber:
diff --git a/pkg/transformers/neosync/neosync_fullname_transformer.go b/pkg/transformers/neosync/neosync_fullname_transformer.go
new file mode 100644
index 0000000..5ee611a
--- /dev/null
+++ b/pkg/transformers/neosync/neosync_fullname_transformer.go
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: Apache-2.0
+
+package neosync
+
+import (
+	"fmt"
+
+	neosynctransformers "github.com/nucleuscloud/neosync/worker/pkg/benthos/transformers"
+	"github.com/xataio/pgstream/pkg/transformers"
+)
+
+type FullNameTransformer struct {
+	*transformer[string]
+}
+
+var fullNameTransformerParams = []string{"preserve_length", "max_length", "seed"}
+
+func NewFullNameTransformer(params transformers.Parameters) (*FullNameTransformer, error) {
+	if err := transformers.ValidateParameters(params, fullNameTransformerParams); err != nil {
+		return nil, err
+	}
+
+	preserveLength, err := findParameter[bool](params, "preserve_length")
+	if err != nil {
+		return nil, fmt.Errorf("neosync_fullname: preserve_length must be a boolean: %w", err)
+	}
+
+	maxLength, err := findParameter[int](params, "max_length")
+	if err != nil {
+		return nil, fmt.Errorf("neosync_fullname: max_length must be an integer: %w", err)
+	}
+
+	seed, err := findParameter[int](params, "seed")
+	if err != nil {
+		return nil, fmt.Errorf("neosync_fullname: seed must be an integer: %w", err)
+	}
+
+	opts, err := neosynctransformers.NewTransformFullNameOpts(toInt64Ptr(maxLength), preserveLength, toInt64Ptr(seed))
+	if err != nil {
+		return nil, err
+	}
+
+	return &FullNameTransformer{
+		transformer: New[string](neosynctransformers.NewTransformFullName(), opts),
+	}, nil
+}
+
+func (t *FullNameTransformer) CompatibleTypes() []transformers.SupportedDataType {
+	return []transformers.SupportedDataType{
+		transformers.StringDataType,
+	}
+}
+
+func (t *FullNameTransformer) Type() transformers.TransformerType {
+	return transformers.NeosyncFullName
+}
diff --git a/pkg/transformers/transformer.go b/pkg/transformers/transformer.go
index abac35f..7ea9485 100644
--- a/pkg/transformers/transformer.go
+++ b/pkg/transformers/transformer.go
@@ -40,6 +40,7 @@ const (
 	NeosyncFirstName       TransformerType = "neosync_firstname"
 	GreenmaskFirstName     TransformerType = "greenmask_firstname"
 	NeosyncLastName        TransformerType = "neosync_lastname"
+	NeosyncFullName        TransformerType = "neosync_fullname"
 	NeosyncEmail           TransformerType = "neosync_email"
 	GreenmaskInteger       TransformerType = "greenmask_integer"
 	GreenmaskFloat         TransformerType = "greenmask_float"


Test patch:
diff --git a/pkg/transformers/neosync/neosync_fullname_transformer_test.go b/pkg/transformers/neosync/neosync_fullname_transformer_test.go
new file mode 100644
index 0000000..042a51a
--- /dev/null
+++ b/pkg/transformers/neosync/neosync_fullname_transformer_test.go
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: Apache-2.0
+
+package neosync
+
+import (
+	"context"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"github.com/xataio/pgstream/pkg/transformers"
+)
+
+func TestNewFullnameTransformer(t *testing.T) {
+	t.Parallel()
+	tests := []struct {
+		name     string
+		params   transformers.Parameters
+		input    any
+		wantErr  error
+		wantName string
+	}{
+		{
+			name: "ok - valid",
+			params: transformers.Parameters{
+				"preserve_length": false,
+				"max_length":      20,
+				"seed":            1234,
+			},
+			input:    "name surname",
+			wantErr:  nil,
+			wantName: "Flav Di Chiara",
+		},
+		{
+			name: "error - invalid preserve_length",
+			params: transformers.Parameters{
+				"preserve_length": 123,
+				"max_length":      10,
+				"seed":            123,
+			},
+			wantErr: transformers.ErrInvalidParameters,
+		},
+		{
+			name: "error - invalid max_length",
+			params: transformers.Parameters{
+				"preserve_length": true,
+				"max_length":      "invalid",
+				"seed":            123,
+			},
+			wantErr: transformers.ErrInvalidParameters,
+		},
+		{
+			name: "error - invalid seed",
+			params: transformers.Parameters{
+				"preserve_length": true,
+				"max_length":      10,
+				"seed":            "invalid",
+			},
+			wantErr: transformers.ErrInvalidParameters,
+		},
+	}
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			lst, err := NewFullNameTransformer(tc.params)
+			require.ErrorIs(t, err, tc.wantErr)
+			if tc.wantErr != nil {
+				return
+			}
+			require.NoError(t, err)
+			require.NotNil(t, lst)
+			got, _ := lst.Transform(context.Background(), transformers.Value{TransformValue: tc.input})
+			require.Equal(t, tc.wantName, got)
+		})
+	}
+}