From 4836892ee202fcb6c28473f6e734b28a0748a058 Mon Sep 17 00:00:00 2001 From: James Harris Date: Thu, 15 Aug 2024 17:05:33 +1000 Subject: [PATCH] Add `ToProto()` and `FromProto()`. --- CHANGELOG.md | 7 + api/client.go | 2 +- api/server.go | 2 +- api/server_test.go | 3 +- internal/entity/doc.go | 5 + api/marshaling.go => marshal.go | 182 +++++++++++++++------- api/marshaling_test.go => marshal_test.go | 128 ++++++++------- 7 files changed, 209 insertions(+), 120 deletions(-) create mode 100644 internal/entity/doc.go rename api/marshaling.go => marshal.go (52%) rename api/marshaling_test.go => marshal_test.go (68%) diff --git a/CHANGELOG.md b/CHANGELOG.md index e148390e..39fa6682 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,13 @@ The format is based on [Keep a Changelog], and this project adheres to [keep a changelog]: https://keepachangelog.com/en/1.0.0/ [semantic versioning]: https://semver.org/spec/v2.0.0.html +## [Unreleased] + +### Added + +- Added `ToProto()` and `FromProto()` to convert application configurations to + and from their protocol buffers representations. + ## [0.13.2] - 2024-08-12 ### Fixed diff --git a/api/client.go b/api/client.go index 81cc7c39..fcba6f15 100644 --- a/api/client.go +++ b/api/client.go @@ -35,7 +35,7 @@ func (c *Client) ListApplications( var configs []configkit.Application for _, in := range res.GetApplications() { - out, err := unmarshalApplication(in) + out, err := configkit.FromProto(in) if err != nil { return nil, err } diff --git a/api/server.go b/api/server.go index fa44fd15..ac45ccad 100644 --- a/api/server.go +++ b/api/server.go @@ -20,7 +20,7 @@ func NewServer(apps ...configkit.Application) *Server { s := &Server{} for _, in := range apps { - out, err := marshalApplication(in) + out, err := configkit.ToProto(in) if err != nil { panic(err) } diff --git a/api/server_test.go b/api/server_test.go index b224103c..33fae9e8 100644 --- a/api/server_test.go +++ b/api/server_test.go @@ -1,6 +1,7 @@ -package api +package api_test import ( + . "github.com/dogmatiq/configkit/api" "github.com/dogmatiq/configkit/internal/entity" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" diff --git a/internal/entity/doc.go b/internal/entity/doc.go new file mode 100644 index 00000000..b9ae91a5 --- /dev/null +++ b/internal/entity/doc.go @@ -0,0 +1,5 @@ +// Package entity provides internal implementations of the entities defined in +// the configkit package. +// +// deprecated: Packages should define their own implementations instead. +package entity diff --git a/api/marshaling.go b/marshal.go similarity index 52% rename from api/marshaling.go rename to marshal.go index 5575f2a7..ddfa52ce 100644 --- a/api/marshaling.go +++ b/marshal.go @@ -1,18 +1,17 @@ -package api +package configkit import ( + "context" "errors" "fmt" - "github.com/dogmatiq/configkit" - "github.com/dogmatiq/configkit/internal/entity" "github.com/dogmatiq/configkit/message" "github.com/dogmatiq/interopspec/configspec" ) -// marshalApplication marshals an application config to its protobuf +// ToProto converts an application configuration to its protocol buffers // representation. -func marshalApplication(in configkit.Application) (*configspec.Application, error) { +func ToProto(in Application) (*configspec.Application, error) { out := &configspec.Application{} var err error @@ -38,25 +37,19 @@ func marshalApplication(in configkit.Application) (*configspec.Application, erro return out, nil } -// unmarshalApplication unmarshals an application config from its protobuf +// FromProto converts an application configuration from its protocol buffers // representation. -func unmarshalApplication(in *configspec.Application) (configkit.Application, error) { - out := &entity.Application{ - MessageNamesValue: configkit.EntityMessageNames{ - Produced: message.NameRoles{}, - Consumed: message.NameRoles{}, - }, - HandlersValue: configkit.HandlerSet{}, - } +func FromProto(in *configspec.Application) (Application, error) { + out := &unmarshaledApplication{} var err error - out.IdentityValue, err = unmarshalIdentity(in.GetIdentity()) + out.ident, err = unmarshalIdentity(in.GetIdentity()) if err != nil { return nil, err } - out.TypeNameValue = in.GetGoType() - if out.TypeNameValue == "" { + out.typeName = in.GetGoType() + if out.typeName == "" { return nil, errors.New("application type name is empty") } @@ -66,14 +59,23 @@ func unmarshalApplication(in *configspec.Application) (configkit.Application, er return nil, err } - out.HandlersValue.Add(hOut) + if out.handlers == nil { + out.handlers = HandlerSet{} + } + out.handlers.Add(hOut) for n, r := range hOut.MessageNames().Produced { - out.MessageNamesValue.Produced[n] = r + if out.names.Produced == nil { + out.names.Produced = message.NameRoles{} + } + out.names.Produced[n] = r } for n, r := range hOut.MessageNames().Consumed { - out.MessageNamesValue.Consumed[n] = r + if out.names.Consumed == nil { + out.names.Consumed = message.NameRoles{} + } + out.names.Consumed[n] = r } } @@ -81,7 +83,7 @@ func unmarshalApplication(in *configspec.Application) (configkit.Application, er } // marshalHandler marshals a handler config to its protobuf representation. -func marshalHandler(in configkit.Handler) (*configspec.Handler, error) { +func marshalHandler(in Handler) (*configspec.Handler, error) { out := &configspec.Handler{ IsDisabled: in.IsDisabled(), } @@ -118,37 +120,33 @@ func marshalHandler(in configkit.Handler) (*configspec.Handler, error) { // unmarshalHandler unmarshals a handler configuration from its protocol buffers // representation. -func unmarshalHandler(in *configspec.Handler) (configkit.Handler, error) { - out := &entity.Handler{ - MessageNamesValue: configkit.EntityMessageNames{ - Produced: message.NameRoles{}, - Consumed: message.NameRoles{}, - }, - IsDisabledValue: in.GetIsDisabled(), +func unmarshalHandler(in *configspec.Handler) (Handler, error) { + out := &unmarshaledHandler{ + isDisabled: in.GetIsDisabled(), } var err error - out.IdentityValue, err = unmarshalIdentity(in.GetIdentity()) + out.ident, err = unmarshalIdentity(in.GetIdentity()) if err != nil { return nil, err } - out.TypeNameValue = in.GetGoType() - if out.TypeNameValue == "" { + out.typeName = in.GetGoType() + if out.typeName == "" { return nil, errors.New("handler type name is empty") } - out.HandlerTypeValue, err = unmarshalHandlerType(in.GetType()) + out.handlerType, err = unmarshalHandlerType(in.GetType()) if err != nil { return nil, err } - out.MessageNamesValue.Produced, err = unmarshalNameRoles(in.GetProducedMessages()) + out.names.Produced, err = unmarshalNameRoles(in.GetProducedMessages()) if err != nil { return nil, err } - out.MessageNamesValue.Consumed, err = unmarshalNameRoles(in.GetConsumedMessages()) + out.names.Consumed, err = unmarshalNameRoles(in.GetConsumedMessages()) if err != nil { return nil, err } @@ -178,7 +176,7 @@ func marshalNameRoles(in message.NameRoles) (map[string]configspec.MessageRole, return out, nil } -// marshalNameRoles unmarshals a message.NameRoles collection from +// unmarshalNameRoles unmarshals a message.NameRoles collection from // its protocol buffers representation. func unmarshalNameRoles(in map[string]configspec.MessageRole) (message.NameRoles, error) { out := message.NameRoles{} @@ -201,9 +199,9 @@ func unmarshalNameRoles(in map[string]configspec.MessageRole) (message.NameRoles return out, nil } -// marshalIdentity marshals a configkit.Identity to its protocol buffers +// marshalIdentity marshals a Identity to its protocol buffers // representation. -func marshalIdentity(in configkit.Identity) (*configspec.Identity, error) { +func marshalIdentity(in Identity) (*configspec.Identity, error) { if err := in.Validate(); err != nil { return nil, err } @@ -214,46 +212,46 @@ func marshalIdentity(in configkit.Identity) (*configspec.Identity, error) { }, nil } -// unmarshalIdentity unmarshals a configkit.Identity from its protocol buffers +// unmarshalIdentity unmarshals a Identity from its protocol buffers // representation. -func unmarshalIdentity(in *configspec.Identity) (configkit.Identity, error) { - return configkit.NewIdentity( +func unmarshalIdentity(in *configspec.Identity) (Identity, error) { + return NewIdentity( in.GetName(), in.GetKey(), ) } -// marshalHandlerType marshals a configkit.HandlerType to its protocol buffers +// marshalHandlerType marshals a HandlerType to its protocol buffers // representation. -func marshalHandlerType(t configkit.HandlerType) (configspec.HandlerType, error) { +func marshalHandlerType(t HandlerType) (configspec.HandlerType, error) { if err := t.Validate(); err != nil { return configspec.HandlerType_UNKNOWN_HANDLER_TYPE, err } switch t { - case configkit.AggregateHandlerType: + case AggregateHandlerType: return configspec.HandlerType_AGGREGATE, nil - case configkit.ProcessHandlerType: + case ProcessHandlerType: return configspec.HandlerType_PROCESS, nil - case configkit.IntegrationHandlerType: + case IntegrationHandlerType: return configspec.HandlerType_INTEGRATION, nil - default: // configkit.ProjectionHandlerType + default: // ProjectionHandlerType return configspec.HandlerType_PROJECTION, nil } } -// unmarshalHandlerType unmarshals a configkit.HandlerType from its protocol +// unmarshalHandlerType unmarshals a HandlerType from its protocol // buffers representation. -func unmarshalHandlerType(t configspec.HandlerType) (configkit.HandlerType, error) { +func unmarshalHandlerType(t configspec.HandlerType) (HandlerType, error) { switch t { case configspec.HandlerType_AGGREGATE: - return configkit.AggregateHandlerType, nil + return AggregateHandlerType, nil case configspec.HandlerType_PROCESS: - return configkit.ProcessHandlerType, nil + return ProcessHandlerType, nil case configspec.HandlerType_INTEGRATION: - return configkit.IntegrationHandlerType, nil + return IntegrationHandlerType, nil case configspec.HandlerType_PROJECTION: - return configkit.ProjectionHandlerType, nil + return ProjectionHandlerType, nil default: return "", fmt.Errorf("unknown handler type: %#v", t) } @@ -290,3 +288,83 @@ func unmarshalMessageRole(r configspec.MessageRole) (message.Role, error) { return "", fmt.Errorf("unknown message role: %#v", r) } } + +// unmarshaledApplication is an implementation of [Application] that has been +// produced by unmarshaling a configuration. +type unmarshaledApplication struct { + ident Identity + names EntityMessageNames + typeName string + handlers HandlerSet +} + +func (a *unmarshaledApplication) Identity() Identity { + return a.ident +} + +func (a *unmarshaledApplication) MessageNames() EntityMessageNames { + return a.names +} + +func (a *unmarshaledApplication) TypeName() string { + return a.typeName +} + +func (a *unmarshaledApplication) AcceptVisitor(ctx context.Context, v Visitor) error { + return v.VisitApplication(ctx, a) +} + +func (a *unmarshaledApplication) Handlers() HandlerSet { + return a.handlers +} + +// unmarshaledHandler is an implementation of [Handler] that has been produced +// by unmarshaling a configuration. +type unmarshaledHandler struct { + ident Identity + names EntityMessageNames + typeName string + handlerType HandlerType + isDisabled bool +} + +// Identity returns the identity of the entity. +func (h *unmarshaledHandler) Identity() Identity { + return h.ident +} + +// MessageNames returns information about the messages used by the entity. +func (h *unmarshaledHandler) MessageNames() EntityMessageNames { + return h.names +} + +// TypeName returns the fully-qualified type name of the entity. +func (h *unmarshaledHandler) TypeName() string { + return h.typeName +} + +// HandlerType returns the type of handler. +func (h *unmarshaledHandler) HandlerType() HandlerType { + return h.handlerType +} + +// IsDisabled returns true if the handler is disabled. +func (h *unmarshaledHandler) IsDisabled() bool { + return h.isDisabled +} + +// AcceptVisitor calls the appropriate method on v for this entity type. +func (h *unmarshaledHandler) AcceptVisitor(ctx context.Context, v Visitor) error { + h.handlerType.MustValidate() + + switch h.handlerType { + case AggregateHandlerType: + return v.VisitAggregate(ctx, h) + case ProcessHandlerType: + return v.VisitProcess(ctx, h) + case IntegrationHandlerType: + return v.VisitIntegration(ctx, h) + default: // ProjectionHandlerType + return v.VisitProjection(ctx, h) + } +} diff --git a/api/marshaling_test.go b/marshal_test.go similarity index 68% rename from api/marshaling_test.go rename to marshal_test.go index 219940ec..cc728f88 100644 --- a/api/marshaling_test.go +++ b/marshal_test.go @@ -1,48 +1,46 @@ -package api +package configkit import ( - "github.com/dogmatiq/configkit" - "github.com/dogmatiq/configkit/internal/entity" "github.com/dogmatiq/configkit/message" - . "github.com/dogmatiq/dogma/fixtures" + "github.com/dogmatiq/dogma/fixtures" "github.com/dogmatiq/interopspec/configspec" . "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" ) -var _ = Describe("func marshalApplication()", func() { - var app *entity.Application +var _ = Describe("func ToProto()", func() { + var app *unmarshaledApplication BeforeEach(func() { - app = &entity.Application{ - IdentityValue: configkit.MustNewIdentity("", "28c19ec0-a32f-4479-bb1d-02887e90077c"), - TypeNameValue: "", - MessageNamesValue: configkit.EntityMessageNames{}, - HandlersValue: configkit.HandlerSet{}, + app = &unmarshaledApplication{ + ident: MustNewIdentity("", "28c19ec0-a32f-4479-bb1d-02887e90077c"), + typeName: "", + names: EntityMessageNames{}, + handlers: HandlerSet{}, } }) It("returns an error if the identity is invalid", func() { - app.IdentityValue.Name = "" - _, err := marshalApplication(app) + app.ident.Name = "" + _, err := ToProto(app) Expect(err).Should(HaveOccurred()) }) It("returns an error if the type name is empty", func() { - app.TypeNameValue = "" - _, err := marshalApplication(app) + app.typeName = "" + _, err := ToProto(app) Expect(err).Should(HaveOccurred()) }) It("returns an error if one of the handlers is invalid", func() { - app.HandlersValue.Add(&entity.Handler{}) - _, err := marshalApplication(app) + app.handlers.Add(&unmarshaledHandler{}) + _, err := ToProto(app) Expect(err).Should(HaveOccurred()) }) }) -var _ = Describe("func unmarshalApplication()", func() { +var _ = Describe("func FromProto()", func() { var app *configspec.Application BeforeEach(func() { @@ -54,77 +52,77 @@ var _ = Describe("func unmarshalApplication()", func() { It("returns an error if the identity is invalid", func() { app.Identity.Name = "" - _, err := unmarshalApplication(app) + _, err := FromProto(app) Expect(err).Should(HaveOccurred()) }) It("returns an error if the type name is empty", func() { app.GoType = "" - _, err := unmarshalApplication(app) + _, err := FromProto(app) Expect(err).Should(HaveOccurred()) }) It("returns an error if one of the handlers is invalid", func() { app.Handlers = append(app.Handlers, &configspec.Handler{}) - _, err := unmarshalApplication(app) + _, err := FromProto(app) Expect(err).Should(HaveOccurred()) }) }) var _ = Describe("func marshalHandler()", func() { - var hnd *entity.Handler + var handler *unmarshaledHandler BeforeEach(func() { - hnd = &entity.Handler{ - IdentityValue: configkit.MustNewIdentity("", "26c19bed-f9e8-45b1-8f60-746f7ca6ef36"), - TypeNameValue: "github.com/dogmatiq/dogma/fixtures.MessageA", - MessageNamesValue: configkit.EntityMessageNames{}, - HandlerTypeValue: configkit.AggregateHandlerType, + handler = &unmarshaledHandler{ + ident: MustNewIdentity("", "26c19bed-f9e8-45b1-8f60-746f7ca6ef36"), + typeName: "github.com/dogmatiq/dogma/fixtures.MessageA", + names: EntityMessageNames{}, + handlerType: AggregateHandlerType, } }) It("returns an error if the identity is invalid", func() { - hnd.IdentityValue.Name = "" - _, err := marshalHandler(hnd) + handler.ident.Name = "" + _, err := marshalHandler(handler) Expect(err).Should(HaveOccurred()) }) It("returns an error if the type name is empty", func() { - hnd.TypeNameValue = "" - _, err := marshalHandler(hnd) + handler.typeName = "" + _, err := marshalHandler(handler) Expect(err).Should(HaveOccurred()) }) It("returns an error if the handler type is invalid", func() { - hnd.HandlerTypeValue = "" - _, err := marshalHandler(hnd) + handler.handlerType = "" + _, err := marshalHandler(handler) Expect(err).Should(HaveOccurred()) }) It("returns an error if the consumed name/roles are invalid", func() { - hnd.MessageNamesValue.Consumed = message.NameRoles{ - message.NameOf(MessageA{}): "", + handler.names.Consumed = message.NameRoles{ + message.NameOf(fixtures.MessageA{}): "", } - _, err := marshalHandler(hnd) + _, err := marshalHandler(handler) Expect(err).Should(HaveOccurred()) }) It("returns an error if the produced name/roles are invalid", func() { - hnd.MessageNamesValue.Produced = message.NameRoles{ - message.NameOf(MessageA{}): "", + handler.names.Produced = message.NameRoles{ + message.NameOf(fixtures.MessageA{}): "", } - _, err := marshalHandler(hnd) + _, err := marshalHandler(handler) Expect(err).Should(HaveOccurred()) }) }) var _ = Describe("func unmarshalHandler()", func() { - var hnd *configspec.Handler + var handler *configspec.Handler BeforeEach(func() { - hnd = &configspec.Handler{ + handler = &configspec.Handler{ Identity: &configspec.Identity{Name: "", Key: "71976ec1-39c6-4f7e-b16f-632ec307e35b"}, GoType: "", Type: configspec.HandlerType_AGGREGATE, @@ -134,38 +132,38 @@ var _ = Describe("func unmarshalHandler()", func() { }) It("returns an error if the identity is invalid", func() { - hnd.Identity.Name = "" - _, err := unmarshalHandler(hnd) + handler.Identity.Name = "" + _, err := unmarshalHandler(handler) Expect(err).Should(HaveOccurred()) }) It("returns an error if the type name is empty", func() { - hnd.GoType = "" - _, err := unmarshalHandler(hnd) + handler.GoType = "" + _, err := unmarshalHandler(handler) Expect(err).Should(HaveOccurred()) }) It("returns an error if the handler type is invalid", func() { - hnd.Type = configspec.HandlerType_UNKNOWN_HANDLER_TYPE - _, err := unmarshalHandler(hnd) + handler.Type = configspec.HandlerType_UNKNOWN_HANDLER_TYPE + _, err := unmarshalHandler(handler) Expect(err).Should(HaveOccurred()) }) It("returns an error if the consumed messages are invalid", func() { - hnd.ConsumedMessages = map[string]configspec.MessageRole{ + handler.ConsumedMessages = map[string]configspec.MessageRole{ "": configspec.MessageRole_UNKNOWN_MESSAGE_ROLE, } - _, err := unmarshalHandler(hnd) + _, err := unmarshalHandler(handler) Expect(err).Should(HaveOccurred()) }) It("returns an error if the produced messages are invalid", func() { - hnd.ProducedMessages = map[string]configspec.MessageRole{ + handler.ProducedMessages = map[string]configspec.MessageRole{ "": configspec.MessageRole_UNKNOWN_MESSAGE_ROLE, } - _, err := unmarshalHandler(hnd) + _, err := unmarshalHandler(handler) Expect(err).Should(HaveOccurred()) }) }) @@ -181,7 +179,7 @@ var _ = Describe("func marshalNameRoles()", func() { It("returns an error if the role can not be marshaled", func() { in := message.NameRoles{ - message.NameOf(MessageA{}): "", + message.NameOf(fixtures.MessageA{}): "", } _, err := marshalNameRoles(in) Expect(err).Should(HaveOccurred()) @@ -210,7 +208,7 @@ var _ = Describe("func unmarshalNameRoles()", func() { var _ = Describe("func marshalIdentity()", func() { It("returns the protobuf representation", func() { - in := configkit.MustNewIdentity("", "9c71b756-b0ab-4c97-9ac8-75fae1dc8814") + in := MustNewIdentity("", "9c71b756-b0ab-4c97-9ac8-75fae1dc8814") out, err := marshalIdentity(in) Expect(err).ShouldNot(HaveOccurred()) Expect(out).To(Equal(&configspec.Identity{ @@ -220,7 +218,7 @@ var _ = Describe("func marshalIdentity()", func() { }) It("returns an error if the identity is invalid", func() { - in := configkit.Identity{} + in := Identity{} _, err := marshalIdentity(in) Expect(err).Should(HaveOccurred()) }) @@ -235,7 +233,7 @@ var _ = Describe("func unmarshalIdentity()", func() { out, err := unmarshalIdentity(in) Expect(err).ShouldNot(HaveOccurred()) Expect(out).To(Equal( - configkit.MustNewIdentity("", "9a63e9ce-40ce-48a7-aa26-88b20a91ec61"), + MustNewIdentity("", "9a63e9ce-40ce-48a7-aa26-88b20a91ec61"), )) }) @@ -249,15 +247,15 @@ var _ = Describe("func unmarshalIdentity()", func() { var _ = Describe("func marshalHandlerType()", func() { DescribeTable( "returns the expected enumeration value", - func(in configkit.HandlerType, expect configspec.HandlerType) { + func(in HandlerType, expect configspec.HandlerType) { out, err := marshalHandlerType(in) Expect(err).ShouldNot(HaveOccurred()) Expect(out).To(Equal(expect)) }, - Entry("aggregate", configkit.AggregateHandlerType, configspec.HandlerType_AGGREGATE), - Entry("process", configkit.ProcessHandlerType, configspec.HandlerType_PROCESS), - Entry("integration", configkit.IntegrationHandlerType, configspec.HandlerType_INTEGRATION), - Entry("projection", configkit.ProjectionHandlerType, configspec.HandlerType_PROJECTION), + Entry("aggregate", AggregateHandlerType, configspec.HandlerType_AGGREGATE), + Entry("process", ProcessHandlerType, configspec.HandlerType_PROCESS), + Entry("integration", IntegrationHandlerType, configspec.HandlerType_INTEGRATION), + Entry("projection", ProjectionHandlerType, configspec.HandlerType_PROJECTION), ) It("returns an error if the message role is invalid", func() { @@ -269,15 +267,15 @@ var _ = Describe("func marshalHandlerType()", func() { var _ = Describe("func unmarshalHandlerType()", func() { DescribeTable( "returns the expected enumeration value", - func(in configspec.HandlerType, expect configkit.HandlerType) { + func(in configspec.HandlerType, expect HandlerType) { out, err := unmarshalHandlerType(in) Expect(err).ShouldNot(HaveOccurred()) Expect(out).To(Equal(expect)) }, - Entry("aggregate", configspec.HandlerType_AGGREGATE, configkit.AggregateHandlerType), - Entry("process", configspec.HandlerType_PROCESS, configkit.ProcessHandlerType), - Entry("integration", configspec.HandlerType_INTEGRATION, configkit.IntegrationHandlerType), - Entry("projection", configspec.HandlerType_PROJECTION, configkit.ProjectionHandlerType), + Entry("aggregate", configspec.HandlerType_AGGREGATE, AggregateHandlerType), + Entry("process", configspec.HandlerType_PROCESS, ProcessHandlerType), + Entry("integration", configspec.HandlerType_INTEGRATION, IntegrationHandlerType), + Entry("projection", configspec.HandlerType_PROJECTION, ProjectionHandlerType), ) It("returns an error if the message role is invalid", func() {