From 9b85096dec5e265b302c0ccb29647186103883d0 Mon Sep 17 00:00:00 2001 From: Tiago Scolari Date: Thu, 17 Jul 2025 10:10:08 +0100 Subject: [PATCH 01/10] Fix anonymous function name detection Signed-off-by: Tiago Scolari --- workflow/worker.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/workflow/worker.go b/workflow/worker.go index a37e5435..7f4639b9 100644 --- a/workflow/worker.go +++ b/workflow/worker.go @@ -20,6 +20,7 @@ import ( "fmt" "log" "reflect" + "regexp" "runtime" "strings" @@ -90,10 +91,15 @@ func getFunctionName(f interface{}) (string, error) { } callSplit := strings.Split(runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name(), ".") - funcName := callSplit[len(callSplit)-1] - if funcName == "1" { + const anonymousFunctionRegxp = "^func[0-9]+$" + isAnonymousFunc, err := regexp.MatchString(anonymousFunctionRegxp, funcName) + if err != nil { + return "", fmt.Errorf("failed to match anonymous function regexp: %w", err) + } + + if isAnonymousFunc { return "", errors.New("anonymous function name") } From 40fed9b0f28d66279afb23ee29272b9e95b3f989 Mon Sep 17 00:00:00 2001 From: Tiago Scolari Date: Thu, 17 Jul 2025 10:10:46 +0100 Subject: [PATCH 02/10] Add registerOptions to allow workflows and activities to be named Signed-off-by: Tiago Scolari --- workflow/worker.go | 61 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/workflow/worker.go b/workflow/worker.go index 7f4639b9..2466d43c 100644 --- a/workflow/worker.go +++ b/workflow/worker.go @@ -113,18 +113,41 @@ func wrapWorkflow(w Workflow) task.Orchestrator { } } +type registerOptions struct { + Name string +} + +type registerOption func(*registerOptions) error + +func RegisterWithName(name string) registerOption { + return func(opts *registerOptions) error { + opts.Name = name + return nil + } +} + // RegisterWorkflow adds a workflow function to the registry -func (ww *WorkflowWorker) RegisterWorkflow(w Workflow) error { +func (ww *WorkflowWorker) RegisterWorkflow(w Workflow, opts ...registerOption) error { wrappedOrchestration := wrapWorkflow(w) - // get the function name for the passed workflow - name, err := getFunctionName(w) - if err != nil { - return fmt.Errorf("failed to get workflow decorator: %v", err) + options := registerOptions{} + for _, opt := range opts { + if err := opt(&options); err != nil { + return fmt.Errorf("failed processing options: %w", err) + } } - err = ww.tasks.AddOrchestratorN(name, wrappedOrchestration) - return err + if options.Name != "" { + // get the function name for the passed workflow if there's + // no explicit name provided. + name, err := getFunctionName(w) + if err != nil { + return fmt.Errorf("failed to get workflow decorator: %v", err) + } + options.Name = name + } + + return ww.tasks.AddOrchestratorN(options.Name, wrappedOrchestration) } func wrapActivity(a Activity) task.Activity { @@ -142,17 +165,27 @@ func wrapActivity(a Activity) task.Activity { } // RegisterActivity adds an activity function to the registry -func (ww *WorkflowWorker) RegisterActivity(a Activity) error { +func (ww *WorkflowWorker) RegisterActivity(a Activity, opts ...registerOption) error { wrappedActivity := wrapActivity(a) - // get the function name for the passed activity - name, err := getFunctionName(a) - if err != nil { - return fmt.Errorf("failed to get activity decorator: %v", err) + options := registerOptions{} + for _, opt := range opts { + if err := opt(&options); err != nil { + return fmt.Errorf("failed processing options: %w", err) + } + } + + if options.Name != "" { + // get the function name for the passed workflow if there's + // no explicit name provided. + name, err := getFunctionName(a) + if err != nil { + return fmt.Errorf("failed to get activity decorator: %v", err) + } + options.Name = name } - err = ww.tasks.AddActivityN(name, wrappedActivity) - return err + return ww.tasks.AddActivityN(options.Name, wrappedActivity) } // Start initialises a non-blocking worker to handle workflows and activities registered From d341805b517e881c392b21a832c407dea408dfee Mon Sep 17 00:00:00 2001 From: Tiago Scolari <38940+tscolari@users.noreply.github.com> Date: Thu, 17 Jul 2025 11:07:36 +0100 Subject: [PATCH 03/10] Fix name check from options Co-authored-by: Joni Collinge Signed-off-by: Tiago Scolari <38940+tscolari@users.noreply.github.com> --- workflow/worker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workflow/worker.go b/workflow/worker.go index 2466d43c..8e19f66f 100644 --- a/workflow/worker.go +++ b/workflow/worker.go @@ -137,7 +137,7 @@ func (ww *WorkflowWorker) RegisterWorkflow(w Workflow, opts ...registerOption) e } } - if options.Name != "" { + if options.Name == "" { // get the function name for the passed workflow if there's // no explicit name provided. name, err := getFunctionName(w) From 3bc00c8259c1cce9dda87e5c0c0ff2178b035323 Mon Sep 17 00:00:00 2001 From: Tiago Scolari Date: Thu, 17 Jul 2025 11:10:39 +0100 Subject: [PATCH 04/10] Fix the other name check, small typo Signed-off-by: Tiago Scolari --- workflow/worker.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/workflow/worker.go b/workflow/worker.go index 8e19f66f..867985e3 100644 --- a/workflow/worker.go +++ b/workflow/worker.go @@ -93,8 +93,8 @@ func getFunctionName(f interface{}) (string, error) { callSplit := strings.Split(runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name(), ".") funcName := callSplit[len(callSplit)-1] - const anonymousFunctionRegxp = "^func[0-9]+$" - isAnonymousFunc, err := regexp.MatchString(anonymousFunctionRegxp, funcName) + const anonymousFunctionRegexp = "^func[0-9]+$" + isAnonymousFunc, err := regexp.MatchString(anonymousFunctionRegexp, funcName) if err != nil { return "", fmt.Errorf("failed to match anonymous function regexp: %w", err) } @@ -175,7 +175,7 @@ func (ww *WorkflowWorker) RegisterActivity(a Activity, opts ...registerOption) e } } - if options.Name != "" { + if options.Name == "" { // get the function name for the passed workflow if there's // no explicit name provided. name, err := getFunctionName(a) From 5fb3c6117829935e78a870841c26755ce11c826d Mon Sep 17 00:00:00 2001 From: Tiago Scolari Date: Thu, 17 Jul 2025 13:54:47 +0100 Subject: [PATCH 05/10] Add comments to the new public RegisterWithName function Signed-off-by: Tiago Scolari --- workflow/worker.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/workflow/worker.go b/workflow/worker.go index 867985e3..2f43b3c7 100644 --- a/workflow/worker.go +++ b/workflow/worker.go @@ -119,6 +119,8 @@ type registerOptions struct { type registerOption func(*registerOptions) error +// RegisterWithName allows you to specify a custom name for the workflow or activity being registered. +// Activities and Workflows registered without an explicit name will use the function name as the name. func RegisterWithName(name string) registerOption { return func(opts *registerOptions) error { opts.Name = name From 0d7e561073693ad6ff3d4768ad6bfa7943689226 Mon Sep 17 00:00:00 2001 From: Tiago Scolari Date: Thu, 17 Jul 2025 16:08:17 +0100 Subject: [PATCH 06/10] Extra RegisterOptions processing to a function Signed-off-by: Tiago Scolari --- workflow/worker.go | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/workflow/worker.go b/workflow/worker.go index 2f43b3c7..52e07a55 100644 --- a/workflow/worker.go +++ b/workflow/worker.go @@ -128,15 +128,23 @@ func RegisterWithName(name string) registerOption { } } +func processRegisterOptions(defaultOptions registerOptions, opts ...registerOption) (registerOptions, error) { + options := defaultOptions + for _, opt := range opts { + if err := opt(&options); err != nil { + return options, fmt.Errorf("failed processing options: %w", err) + } + } + return options, nil +} + // RegisterWorkflow adds a workflow function to the registry func (ww *WorkflowWorker) RegisterWorkflow(w Workflow, opts ...registerOption) error { wrappedOrchestration := wrapWorkflow(w) - options := registerOptions{} - for _, opt := range opts { - if err := opt(&options); err != nil { - return fmt.Errorf("failed processing options: %w", err) - } + options, err := processRegisterOptions(registerOptions{}, opts...) + if err != nil { + return err } if options.Name == "" { @@ -170,11 +178,9 @@ func wrapActivity(a Activity) task.Activity { func (ww *WorkflowWorker) RegisterActivity(a Activity, opts ...registerOption) error { wrappedActivity := wrapActivity(a) - options := registerOptions{} - for _, opt := range opts { - if err := opt(&options); err != nil { - return fmt.Errorf("failed processing options: %w", err) - } + options, err := processRegisterOptions(registerOptions{}, opts...) + if err != nil { + return err } if options.Name == "" { From d5858cc9204a55d61e5ca666deba8f753cc240dc Mon Sep 17 00:00:00 2001 From: Tiago Scolari Date: Thu, 17 Jul 2025 16:20:29 +0100 Subject: [PATCH 07/10] Add tests to explicit naming of workflows/activities Revert changes on the anonymous function detection. Signed-off-by: Tiago Scolari --- workflow/worker.go | 9 +-------- workflow/worker_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/workflow/worker.go b/workflow/worker.go index 52e07a55..c1787cea 100644 --- a/workflow/worker.go +++ b/workflow/worker.go @@ -20,7 +20,6 @@ import ( "fmt" "log" "reflect" - "regexp" "runtime" "strings" @@ -93,13 +92,7 @@ func getFunctionName(f interface{}) (string, error) { callSplit := strings.Split(runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name(), ".") funcName := callSplit[len(callSplit)-1] - const anonymousFunctionRegexp = "^func[0-9]+$" - isAnonymousFunc, err := regexp.MatchString(anonymousFunctionRegexp, funcName) - if err != nil { - return "", fmt.Errorf("failed to match anonymous function regexp: %w", err) - } - - if isAnonymousFunc { + if funcName == "1" { return "", errors.New("anonymous function name") } diff --git a/workflow/worker_test.go b/workflow/worker_test.go index 697ad916..4c053528 100644 --- a/workflow/worker_test.go +++ b/workflow/worker_test.go @@ -43,22 +43,46 @@ func TestWorkflowRuntime(t *testing.T) { t.Run("register workflow", func(t *testing.T) { err := testWorker.RegisterWorkflow(testWorkflow) require.NoError(t, err) + + t.Run("with explicit name", func(t *testing.T) { + err := testWorker.RegisterWorkflow(testWorkflow, RegisterWithName("MyWorkflow")) + require.NoError(t, err) + }) }) t.Run("register workflow - anonymous func", func(t *testing.T) { err := testWorker.RegisterWorkflow(func(ctx *WorkflowContext) (any, error) { return nil, nil }) require.Error(t, err) + + t.Run("with explicit name", func(t *testing.T) { + err := testWorker.RegisterWorkflow(func(ctx *WorkflowContext) (any, error) { + return nil, nil + }, RegisterWithName("MyWorkflow2")) + require.NoError(t, err) + }) }) t.Run("register activity", func(t *testing.T) { err := testWorker.RegisterActivity(testActivity) require.NoError(t, err) + + t.Run("with explicit name", func(t *testing.T) { + err := testWorker.RegisterActivity(testActivity, RegisterWithName("MyActivity")) + require.NoError(t, err) + }) }) t.Run("register activity - anonymous func", func(t *testing.T) { err := testWorker.RegisterActivity(func(ctx ActivityContext) (any, error) { return nil, nil }) require.Error(t, err) + + t.Run("with explicit name", func(t *testing.T) { + err := testWorker.RegisterActivity(func(ctx ActivityContext) (any, error) { + return nil, nil + }, RegisterWithName("MyActivity2")) + require.NoError(t, err) + }) }) } @@ -69,6 +93,16 @@ func TestWorkerOptions(t *testing.T) { }) } +func TestRegisterOptions(t *testing.T) { + t.Run("with name", func(t *testing.T) { + defaultOpts := registerOptions{} + options, err := processRegisterOptions(defaultOpts, RegisterWithName("testWorkflow")) + require.NoError(t, err) + assert.NotEmpty(t, options.Name) + assert.Equal(t, "testWorkflow", options.Name) + }) +} + func returnWorkerOptions(opts ...workerOption) workerOptions { options := new(workerOptions) for _, configure := range opts { @@ -104,6 +138,12 @@ func TestGetFunctionName(t *testing.T) { require.Error(t, err) assert.Equal(t, "", name) }) + + t.Run("get function name - anonymous", func(t *testing.T) { + name, err := getFunctionName(func() {}) + require.Error(t, err) + assert.Equal(t, "", name) + }) } func testWorkflow(ctx *WorkflowContext) (any, error) { From b82732f3e8b1196b3067265ff1ee4e030b0421bb Mon Sep 17 00:00:00 2001 From: Tiago Scolari Date: Thu, 17 Jul 2025 16:21:24 +0100 Subject: [PATCH 08/10] Rename RegisterWithName to WithName Signed-off-by: Tiago Scolari --- workflow/worker.go | 4 ++-- workflow/worker_test.go | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/workflow/worker.go b/workflow/worker.go index c1787cea..c3eccc04 100644 --- a/workflow/worker.go +++ b/workflow/worker.go @@ -112,9 +112,9 @@ type registerOptions struct { type registerOption func(*registerOptions) error -// RegisterWithName allows you to specify a custom name for the workflow or activity being registered. +// WithName allows you to specify a custom name for the workflow or activity being registered. // Activities and Workflows registered without an explicit name will use the function name as the name. -func RegisterWithName(name string) registerOption { +func WithName(name string) registerOption { return func(opts *registerOptions) error { opts.Name = name return nil diff --git a/workflow/worker_test.go b/workflow/worker_test.go index 4c053528..b254701f 100644 --- a/workflow/worker_test.go +++ b/workflow/worker_test.go @@ -45,7 +45,7 @@ func TestWorkflowRuntime(t *testing.T) { require.NoError(t, err) t.Run("with explicit name", func(t *testing.T) { - err := testWorker.RegisterWorkflow(testWorkflow, RegisterWithName("MyWorkflow")) + err := testWorker.RegisterWorkflow(testWorkflow, WithName("MyWorkflow")) require.NoError(t, err) }) }) @@ -58,7 +58,7 @@ func TestWorkflowRuntime(t *testing.T) { t.Run("with explicit name", func(t *testing.T) { err := testWorker.RegisterWorkflow(func(ctx *WorkflowContext) (any, error) { return nil, nil - }, RegisterWithName("MyWorkflow2")) + }, WithName("MyWorkflow2")) require.NoError(t, err) }) }) @@ -67,7 +67,7 @@ func TestWorkflowRuntime(t *testing.T) { require.NoError(t, err) t.Run("with explicit name", func(t *testing.T) { - err := testWorker.RegisterActivity(testActivity, RegisterWithName("MyActivity")) + err := testWorker.RegisterActivity(testActivity, WithName("MyActivity")) require.NoError(t, err) }) }) @@ -80,7 +80,7 @@ func TestWorkflowRuntime(t *testing.T) { t.Run("with explicit name", func(t *testing.T) { err := testWorker.RegisterActivity(func(ctx ActivityContext) (any, error) { return nil, nil - }, RegisterWithName("MyActivity2")) + }, WithName("MyActivity2")) require.NoError(t, err) }) }) @@ -96,7 +96,7 @@ func TestWorkerOptions(t *testing.T) { func TestRegisterOptions(t *testing.T) { t.Run("with name", func(t *testing.T) { defaultOpts := registerOptions{} - options, err := processRegisterOptions(defaultOpts, RegisterWithName("testWorkflow")) + options, err := processRegisterOptions(defaultOpts, WithName("testWorkflow")) require.NoError(t, err) assert.NotEmpty(t, options.Name) assert.Equal(t, "testWorkflow", options.Name) From d97e28573e071b5ba437342e6c0e991817cc2574 Mon Sep 17 00:00:00 2001 From: Tiago Scolari Date: Thu, 17 Jul 2025 16:46:14 +0100 Subject: [PATCH 09/10] Remove unnecessary assign Signed-off-by: Tiago Scolari --- workflow/worker.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/workflow/worker.go b/workflow/worker.go index c3eccc04..3e33d701 100644 --- a/workflow/worker.go +++ b/workflow/worker.go @@ -121,8 +121,7 @@ func WithName(name string) registerOption { } } -func processRegisterOptions(defaultOptions registerOptions, opts ...registerOption) (registerOptions, error) { - options := defaultOptions +func processRegisterOptions(options registerOptions, opts ...registerOption) (registerOptions, error) { for _, opt := range opts { if err := opt(&options); err != nil { return options, fmt.Errorf("failed processing options: %w", err) From 4a7c58c5a3c7dd6fcbd7fe8279a86239f56deee5 Mon Sep 17 00:00:00 2001 From: Tiago Scolari Date: Fri, 18 Jul 2025 09:30:14 +0100 Subject: [PATCH 10/10] Add extra test to increase test coverage Signed-off-by: Tiago Scolari --- workflow/worker_test.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/workflow/worker_test.go b/workflow/worker_test.go index b254701f..0a24a444 100644 --- a/workflow/worker_test.go +++ b/workflow/worker_test.go @@ -15,6 +15,7 @@ limitations under the License. package workflow import ( + "errors" "testing" daprClient "github.com/dapr/go-sdk/client" @@ -94,13 +95,24 @@ func TestWorkerOptions(t *testing.T) { } func TestRegisterOptions(t *testing.T) { - t.Run("with name", func(t *testing.T) { + t.Run("WithName", func(t *testing.T) { defaultOpts := registerOptions{} options, err := processRegisterOptions(defaultOpts, WithName("testWorkflow")) require.NoError(t, err) assert.NotEmpty(t, options.Name) assert.Equal(t, "testWorkflow", options.Name) }) + + t.Run("error handling", func(t *testing.T) { + optionThatFails := func(opts *registerOptions) error { + return errors.New("this always fails") + } + + defaultOpts := registerOptions{} + _, err := processRegisterOptions(defaultOpts, optionThatFails) + require.Error(t, err) + require.ErrorContains(t, err, "this always fails") + }) } func returnWorkerOptions(opts ...workerOption) workerOptions {