From 011a7e57d14c9b922aa6c6d42ff1f5e88b62ce7d Mon Sep 17 00:00:00 2001 From: Danil Petrov Date: Thu, 28 Dec 2023 16:42:27 +1000 Subject: [PATCH] Fix parsing indirect calls to handler `.Configure()` method. --- static/analysis.go | 12 +- static/handler_test.go | 135 ++++++++++++++++++ .../aggregate.go | 48 +++++++ .../non-pointer-registered-as-pointer/app.go | 17 +++ .../integration.go | 47 ++++++ .../process.go | 72 ++++++++++ .../projection.go | 58 ++++++++ 7 files changed, 383 insertions(+), 6 deletions(-) create mode 100644 static/testdata/handlers/non-pointer-registered-as-pointer/aggregate.go create mode 100644 static/testdata/handlers/non-pointer-registered-as-pointer/app.go create mode 100644 static/testdata/handlers/non-pointer-registered-as-pointer/integration.go create mode 100644 static/testdata/handlers/non-pointer-registered-as-pointer/process.go create mode 100644 static/testdata/handlers/non-pointer-registered-as-pointer/projection.go diff --git a/static/analysis.go b/static/analysis.go index d5d92479..0a091f8f 100644 --- a/static/analysis.go +++ b/static/analysis.go @@ -288,6 +288,12 @@ func addHandlerFromConfigureMethod( switch c.Common().Method.Name() { case "Identity": hdr.IdentityValue = analyzeIdentityCall(c) + case "Routes": + addMessagesFromRoutes( + c.Common().Value.Parent(), + hdr.MessageNamesValue.Produced, + hdr.MessageNamesValue.Consumed, + ) case "ConsumesCommandType": addMessageFromArguments( args, @@ -326,12 +332,6 @@ func addHandlerFromConfigureMethod( } } - addMessagesFromRoutes( - method, - hdr.MessageNamesValue.Produced, - hdr.MessageNamesValue.Consumed, - ) - hs.Add(hdr) } diff --git a/static/handler_test.go b/static/handler_test.go index 18aee9bb..4f7ae7e7 100644 --- a/static/handler_test.go +++ b/static/handler_test.go @@ -855,4 +855,139 @@ var _ = Describe("func FromPackages() (handler analysis)", func() { Expect(apps[0].Handlers()).To(Equal(configkit.HandlerSet{})) }) }) + + When("the application contains handlers registered as pointers with method receivers passed by values", func() { + It("correctly returns configurations for such handlers", func() { + cfg := packages.Config{ + Mode: packages.LoadAllSyntax, + Dir: "testdata/handlers/non-pointer-registered-as-pointer", + } + + pkgs := loadPackages(cfg) + + apps := FromPackages(pkgs) + Expect(apps).To(HaveLen(1)) + Expect(apps[0].Handlers().Aggregates()).To(HaveLen(1)) + Expect(apps[0].Handlers().Processes()).To(HaveLen(1)) + Expect(apps[0].Handlers().Projections()).To(HaveLen(1)) + Expect(apps[0].Handlers().Integrations()).To(HaveLen(1)) + + aggregate := apps[0].Handlers().Aggregates()[0] + Expect(aggregate.Identity()).To( + Equal( + configkit.Identity{ + Name: "", + Key: "ee1814e3-194d-438a-916e-ee7766598646", + }, + ), + ) + Expect(aggregate.TypeName()).To( + Equal( + "*github.com/dogmatiq/configkit/static/testdata/handlers/non-pointer-registered-as-pointer.AggregateHandler", + ), + ) + Expect(aggregate.HandlerType()).To(Equal(configkit.AggregateHandlerType)) + + Expect(aggregate.MessageNames()).To(Equal( + configkit.EntityMessageNames{ + Consumed: message.NameRoles{ + cfixtures.MessageATypeName: message.CommandRole, + cfixtures.MessageBTypeName: message.CommandRole, + }, + Produced: message.NameRoles{ + cfixtures.MessageCTypeName: message.EventRole, + cfixtures.MessageDTypeName: message.EventRole, + }, + }, + )) + + process := apps[0].Handlers().Processes()[0] + Expect(process.Identity()).To( + Equal( + configkit.Identity{ + Name: "", + Key: "39af6b34-5fa1-4f3a-b049-40a5e1d9b33b", + }, + ), + ) + Expect(process.TypeName()).To( + Equal( + "*github.com/dogmatiq/configkit/static/testdata/handlers/non-pointer-registered-as-pointer.ProcessHandler", + ), + ) + Expect(process.HandlerType()).To(Equal(configkit.ProcessHandlerType)) + + Expect(process.MessageNames()).To(Equal( + configkit.EntityMessageNames{ + Consumed: message.NameRoles{ + cfixtures.MessageATypeName: message.EventRole, + cfixtures.MessageBTypeName: message.EventRole, + cfixtures.MessageETypeName: message.TimeoutRole, + cfixtures.MessageFTypeName: message.TimeoutRole, + }, + Produced: message.NameRoles{ + cfixtures.MessageCTypeName: message.CommandRole, + cfixtures.MessageDTypeName: message.CommandRole, + cfixtures.MessageETypeName: message.TimeoutRole, + cfixtures.MessageFTypeName: message.TimeoutRole, + }, + }, + )) + + projection := apps[0].Handlers().Projections()[0] + Expect(projection.Identity()).To( + Equal( + configkit.Identity{ + Name: "", + Key: "3dfcd7cd-1f63-47a1-9be7-3242bd252423", + }, + ), + ) + Expect(projection.TypeName()).To( + Equal( + "*github.com/dogmatiq/configkit/static/testdata/handlers/non-pointer-registered-as-pointer.ProjectionHandler", + ), + ) + Expect(projection.HandlerType()).To(Equal(configkit.ProjectionHandlerType)) + + Expect(projection.MessageNames()).To(Equal( + configkit.EntityMessageNames{ + Consumed: message.NameRoles{ + cfixtures.MessageATypeName: message.EventRole, + cfixtures.MessageBTypeName: message.EventRole, + }, + Produced: message.NameRoles{}, + }, + )) + + integration := apps[0].Handlers().Integrations()[0] + Expect(integration.Identity()).To( + Equal( + configkit.Identity{ + Name: "", + Key: "1425ca64-0448-4bfd-b18d-9fe63a95995f", + }, + ), + ) + Expect(integration.TypeName()).To( + Equal( + "*github.com/dogmatiq/configkit/static/testdata/handlers/non-pointer-registered-as-pointer.IntegrationHandler", + ), + ) + Expect(integration.HandlerType()).To(Equal(configkit.IntegrationHandlerType)) + + Expect(integration.MessageNames()).To(Equal( + configkit.EntityMessageNames{ + Consumed: message.NameRoles{ + cfixtures.MessageATypeName: message.CommandRole, + cfixtures.MessageBTypeName: message.CommandRole, + }, + Produced: message.NameRoles{ + cfixtures.MessageCTypeName: message.EventRole, + cfixtures.MessageDTypeName: message.EventRole, + }, + }, + )) + }) + }) }) diff --git a/static/testdata/handlers/non-pointer-registered-as-pointer/aggregate.go b/static/testdata/handlers/non-pointer-registered-as-pointer/aggregate.go new file mode 100644 index 00000000..ed1c2026 --- /dev/null +++ b/static/testdata/handlers/non-pointer-registered-as-pointer/aggregate.go @@ -0,0 +1,48 @@ +package app + +import ( + "github.com/dogmatiq/dogma" + "github.com/dogmatiq/dogma/fixtures" +) + +// Aggregate is an aggregate used for testing. +type Aggregate struct{} + +// ApplyEvent updates the aggregate instance to reflect the occurrence of an +// event that was recorded against this instance. +func (Aggregate) ApplyEvent(m dogma.Message) {} + +// AggregateHandler is a test implementation of dogma.AggregateMessageHandler. +type AggregateHandler struct{} + +// New returns a new account instance. +func (AggregateHandler) New() dogma.AggregateRoot { + return Aggregate{} +} + +// Configure configures the behavior of the engine as it relates to this +// handler. +func (AggregateHandler) Configure(c dogma.AggregateConfigurer) { + c.Identity("", "ee1814e3-194d-438a-916e-ee7766598646") + + c.Routes( + dogma.HandlesCommand[fixtures.MessageA](), + dogma.HandlesCommand[fixtures.MessageB](), + dogma.RecordsEvent[fixtures.MessageC](), + dogma.RecordsEvent[fixtures.MessageD](), + ) +} + +// RouteCommandToInstance returns the ID of the aggregate instance that is +// targetted by m. +func (AggregateHandler) RouteCommandToInstance(m dogma.Message) string { + return "" +} + +// HandleCommand handles a command message that has been routed to this handler. +func (AggregateHandler) HandleCommand( + r dogma.AggregateRoot, + s dogma.AggregateCommandScope, + m dogma.Message, +) { +} diff --git a/static/testdata/handlers/non-pointer-registered-as-pointer/app.go b/static/testdata/handlers/non-pointer-registered-as-pointer/app.go new file mode 100644 index 00000000..57508afb --- /dev/null +++ b/static/testdata/handlers/non-pointer-registered-as-pointer/app.go @@ -0,0 +1,17 @@ +package app + +import "github.com/dogmatiq/dogma" + +// App implements dogma.Application interface. +type App struct{} + +// Configure configures the behavior of the engine as it relates to this +// application. +func (App) Configure(c dogma.ApplicationConfigurer) { + c.Identity("", "282653ad-9343-44f1-889e-a8b2b095b54b") + + c.RegisterIntegration(&IntegrationHandler{}) + c.RegisterProjection(&ProjectionHandler{}) + c.RegisterAggregate(&AggregateHandler{}) + c.RegisterProcess(&ProcessHandler{}) +} diff --git a/static/testdata/handlers/non-pointer-registered-as-pointer/integration.go b/static/testdata/handlers/non-pointer-registered-as-pointer/integration.go new file mode 100644 index 00000000..a3c821f1 --- /dev/null +++ b/static/testdata/handlers/non-pointer-registered-as-pointer/integration.go @@ -0,0 +1,47 @@ +package app + +import ( + "context" + "time" + + "github.com/dogmatiq/dogma" + "github.com/dogmatiq/dogma/fixtures" +) + +// IntegrationHandler is a test implementation of +// dogma.IntegrationMessageHandler. +type IntegrationHandler struct{} + +// Configure configures the behavior of the engine as it relates to this +// handler. +func (IntegrationHandler) Configure(c dogma.IntegrationConfigurer) { + c.Identity("", "1425ca64-0448-4bfd-b18d-9fe63a95995f") + + c.Routes( + dogma.HandlesCommand[fixtures.MessageA](), + dogma.HandlesCommand[fixtures.MessageB](), + dogma.RecordsEvent[fixtures.MessageC](), + dogma.RecordsEvent[fixtures.MessageD](), + ) +} + +// RouteCommandToInstance returns the ID of the integration instance that is +// targetted by m. +func (IntegrationHandler) RouteCommandToInstance(m dogma.Message) string { + return "" +} + +// HandleCommand handles a command message that has been routed to this handler. +func (IntegrationHandler) HandleCommand( + ctx context.Context, + s dogma.IntegrationCommandScope, + m dogma.Message, +) error { + return nil +} + +// TimeoutHint returns a duration that is suitable for computing a deadline +// for the handling of the given message by this handler. +func (IntegrationHandler) TimeoutHint(m dogma.Message) time.Duration { + return 0 +} diff --git a/static/testdata/handlers/non-pointer-registered-as-pointer/process.go b/static/testdata/handlers/non-pointer-registered-as-pointer/process.go new file mode 100644 index 00000000..fd51892f --- /dev/null +++ b/static/testdata/handlers/non-pointer-registered-as-pointer/process.go @@ -0,0 +1,72 @@ +package app + +import ( + "context" + "time" + + "github.com/dogmatiq/dogma" + "github.com/dogmatiq/dogma/fixtures" +) + +// Process is a process used for testing. +type Process struct{} + +// ProcessHandler is a test implementation of dogma.ProcessMessageHandler. +type ProcessHandler struct{} + +// New constructs a new process instance initialized with any default values and +// returns the process root. +func (ProcessHandler) New() dogma.ProcessRoot { + return Process{} +} + +// Configure configures the behavior of the engine as it relates to this +// handler. +func (ProcessHandler) Configure(c dogma.ProcessConfigurer) { + c.Identity("", "39af6b34-5fa1-4f3a-b049-40a5e1d9b33b") + + c.Routes( + dogma.HandlesEvent[fixtures.MessageA](), + dogma.HandlesEvent[fixtures.MessageB](), + dogma.ExecutesCommand[fixtures.MessageC](), + dogma.ExecutesCommand[fixtures.MessageD](), + dogma.SchedulesTimeout[fixtures.MessageE](), + dogma.SchedulesTimeout[fixtures.MessageF](), + ) +} + +// RouteEventToInstance returns the ID of the process instance that is +// targeted by m. +func (ProcessHandler) RouteEventToInstance( + ctx context.Context, + m dogma.Message, +) (string, bool, error) { + return "", true, nil +} + +// HandleEvent handles an event message. +func (ProcessHandler) HandleEvent( + ctx context.Context, + r dogma.ProcessRoot, + s dogma.ProcessEventScope, + m dogma.Message, +) error { + return nil +} + +// HandleTimeout handles a timeout message that has been scheduled with +// ProcessScope.ScheduleTimeout(). +func (ProcessHandler) HandleTimeout( + ctx context.Context, + r dogma.ProcessRoot, + s dogma.ProcessTimeoutScope, + m dogma.Message, +) error { + return nil +} + +// TimeoutHint returns a duration that is suitable for computing a deadline +// for the handling of the given message by this handler. +func (ProcessHandler) TimeoutHint(m dogma.Message) time.Duration { + return 0 +} diff --git a/static/testdata/handlers/non-pointer-registered-as-pointer/projection.go b/static/testdata/handlers/non-pointer-registered-as-pointer/projection.go new file mode 100644 index 00000000..c0d641f5 --- /dev/null +++ b/static/testdata/handlers/non-pointer-registered-as-pointer/projection.go @@ -0,0 +1,58 @@ +package app + +import ( + "context" + "time" + + "github.com/dogmatiq/dogma" + "github.com/dogmatiq/dogma/fixtures" +) + +// ProjectionHandler is a test implementation of dogma.ProjectionMessageHandler. +type ProjectionHandler struct{} + +// Configure configures the behavior of the engine as it relates to this +// handler. +func (ProjectionHandler) Configure(c dogma.ProjectionConfigurer) { + c.Identity("", "3dfcd7cd-1f63-47a1-9be7-3242bd252423") + + c.Routes( + dogma.HandlesEvent[fixtures.MessageA](), + dogma.HandlesEvent[fixtures.MessageB](), + ) +} + +// HandleEvent updates the projection to reflect the occurrence of an event. +func (ProjectionHandler) HandleEvent( + ctx context.Context, + r, c, n []byte, + s dogma.ProjectionEventScope, + m dogma.Message, +) (ok bool, err error) { + return false, nil +} + +// ResourceVersion returns the version of the resource r. +func (ProjectionHandler) ResourceVersion( + ctx context.Context, + r []byte, +) ([]byte, error) { + return nil, nil +} + +// CloseResource informs the projection that the resource r will not be +// used in any future calls to HandleEvent(). +func (ProjectionHandler) CloseResource(ctx context.Context, r []byte) error { + return nil +} + +// TimeoutHint returns a duration that is suitable for computing a deadline +// for the handling of the given message by this handler. +func (ProjectionHandler) TimeoutHint(m dogma.Message) time.Duration { + return 0 +} + +// Compact reduces the size of the projection's data. +func (ProjectionHandler) Compact(ctx context.Context, s dogma.ProjectionCompactScope) error { + return nil +}