diff --git a/cmd/asyncapi-codegen/config.go b/cmd/asyncapi-codegen/config.go index d29dabef..9d1c8a0e 100644 --- a/cmd/asyncapi-codegen/config.go +++ b/cmd/asyncapi-codegen/config.go @@ -43,6 +43,9 @@ type Flags struct { // NamingScheme defines the naming case for generated golang structs // Supported values: camel, none NamingScheme string + + // IgnoreStringFormat states whether the properties' format (date, date-time) should impact the type in types + IgnoreStringFormat bool } // SetToCommand adds the flags to a cobra command. @@ -58,16 +61,19 @@ func (f *Flags) SetToCommand(cmd *cobra.Command) { "Schema property key names conversion strategy.\nSupported values: snake, camel, kebab, none.") cmd.Flags().StringVarP(&f.NamingScheme, "naming-scheme", "n", "none", "Naming scheme for generated golang elements.\nSupported values: camel, none.") + cmd.Flags().BoolVar(&f.IgnoreStringFormat, "ignore-string-format", false, + "Ignores the format (date, date-time) on string properties, generating golang string, instead of dates") } // ToCodegenOptions processes command line flags structure to code generation tool options. func (f Flags) ToCodegenOptions() (options.Options, error) { opt := options.Options{ - OutputPath: f.OutputPath, - PackageName: f.PackageName, - DisableFormatting: f.DisableFormatting, - ConvertKeys: f.ConvertKeys, - NamingScheme: f.NamingScheme, + OutputPath: f.OutputPath, + PackageName: f.PackageName, + DisableFormatting: f.DisableFormatting, + ConvertKeys: f.ConvertKeys, + NamingScheme: f.NamingScheme, + IgnoreStringFormat: f.IgnoreStringFormat, } if f.Generate != "" { diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go index b5fc6b47..a9df3515 100644 --- a/pkg/codegen/codegen.go +++ b/pkg/codegen/codegen.go @@ -88,6 +88,10 @@ func (cg CodeGen) Generate(opt options.Options) error { return err } + if opt.IgnoreStringFormat { + template.DisableDateOrTimeGeneration() + } + // Process specification if err := cg.specification.Process(); err != nil { return err diff --git a/pkg/codegen/generators/v2/templates/message.tmpl b/pkg/codegen/generators/v2/templates/message.tmpl index 3bea654a..0b00570d 100644 --- a/pkg/codegen/generators/v2/templates/message.tmpl +++ b/pkg/codegen/generators/v2/templates/message.tmpl @@ -54,7 +54,7 @@ func brokerMessageTo{{namify .Name}}(bMsg extensions.BrokerMessage) ({{namify .N {{- /* Handle payload based on type */}} {{- if eq $payload.Type "string"}} // Convert to string - {{- if and $payload.Format (or (eq $payload.Format "date") (eq $payload.Format "date-time"))}} + {{- if isDateOrDateTimeGenerated $payload.Format }} t, err := time.Parse(time.RFC3339, string(bMsg.Payload)) if err != nil { return {{namify .Name}}{}, err @@ -115,7 +115,7 @@ func brokerMessageTo{{namify .Name}}(bMsg extensions.BrokerMessage) ({{namify .N if err != nil { return msg, err } - {{- else if or (eq $value.Format "date") (eq $value.Format "date-time")}} + {{- else if isDateOrDateTimeGenerated $value.Format }} t, err := time.Parse(time.RFC3339, string(v)) if err != nil { return msg, err @@ -130,7 +130,7 @@ func brokerMessageTo{{namify .Name}}(bMsg extensions.BrokerMessage) ({{namify .N if err != nil { return msg, err } - {{- else if or (eq $value.Format "date") (eq $value.Format "date-time")}} + {{- else if isDateOrDateTimeGenerated $value.Format }} t, err := time.Parse(time.RFC3339, string(v)) if err != nil { return msg, err @@ -188,7 +188,7 @@ func (msg {{namify .Name}}) toBrokerMessage() (extensions.BrokerMessage, error) payload := make([]byte, 8) binary.BigEndian.PutUint64(payload, math.Float64bits(msg.Payload)) {{- end}} - {{- else if and (eq $payload.Type "string") (and $payload.Format (or (eq $payload.Format "date") (eq $payload.Format "date-time")))}} + {{- else if and (eq $payload.Type "string") (isDateOrDateTimeGenerated $payload.Format) }} // Convert to RFC3339 and to []byte payload := []byte(msg.Payload.Format(time.RFC3339)) {{- else}} @@ -218,7 +218,7 @@ func (msg {{namify .Name}}) toBrokerMessage() (extensions.BrokerMessage, error) return extensions.BrokerMessage{}, err } headers["{{$key}}"] = h - {{- else if or (eq $value.Format "date") (eq $value.Format "date-time")}} + {{- else if isDateOrDateTimeGenerated $value.Format }} headers["{{$key}}"] = []byte(msg.Headers.{{namify $key}}.Format(time.RFC3339)) {{- else }} headers["{{$key}}"] = []byte(msg.Headers.{{namify $key}}) @@ -231,7 +231,7 @@ func (msg {{namify .Name}}) toBrokerMessage() (extensions.BrokerMessage, error) return extensions.BrokerMessage{}, err } headers["{{$key}}"] = h - {{- else if or (eq $value.Format "date") (eq $value.Format "date-time")}} + {{- else if isDateOrDateTimeGenerated $value.Format }} headers["{{$key}}"] = []byte(msg.Headers.{{namify $key}}.Format(time.RFC3339)) {{- else }} headers["{{$key}}"] = []byte(*msg.Headers.{{namify $key}}) diff --git a/pkg/codegen/generators/v2/templates/schema_definition.tmpl b/pkg/codegen/generators/v2/templates/schema_definition.tmpl index 2fac5dda..e37d7c26 100644 --- a/pkg/codegen/generators/v2/templates/schema_definition.tmpl +++ b/pkg/codegen/generators/v2/templates/schema_definition.tmpl @@ -35,7 +35,7 @@ type {{ namify .Name }} struct { type {{ .Name }} {{template "schema-name" .}} {{/* Create specific marshaling for time */ -}} -{{- if or (eq .Format "date") (eq .Format "date-time") -}} +{{- if isDateOrDateTimeGenerated .Format -}} {{template "marshaling-time" .}} {{- end -}} diff --git a/pkg/codegen/generators/v2/templates/schema_name.tmpl b/pkg/codegen/generators/v2/templates/schema_name.tmpl index 0577af6b..79c6940f 100644 --- a/pkg/codegen/generators/v2/templates/schema_name.tmpl +++ b/pkg/codegen/generators/v2/templates/schema_name.tmpl @@ -16,9 +16,9 @@ bool {{- /* --------------------------- Type String -------------------------- */ -}} {{- else if eq .Type "string" -}} -{{- if and .Format (eq .Format "date") -}} +{{- if and (isDateOrDateTimeGenerated .Format) (eq .Format "date") -}} civil.Date -{{- else if and .Format (eq .Format "date-time") -}} +{{- else if and (isDateOrDateTimeGenerated .Format) (eq .Format "date-time") -}} time.Time {{- else -}} string diff --git a/pkg/codegen/generators/v3/templates/message.tmpl b/pkg/codegen/generators/v3/templates/message.tmpl index 4198235c..31100bf3 100644 --- a/pkg/codegen/generators/v3/templates/message.tmpl +++ b/pkg/codegen/generators/v3/templates/message.tmpl @@ -63,7 +63,7 @@ func brokerMessageTo{{namify .Name}}(bMsg extensions.BrokerMessage) ({{namify .N {{- /* Handle payload based on type */}} {{- if eq $payload.Type "string"}} // Convert to string - {{- if and $payload.Format (or (eq $payload.Format "date") (eq $payload.Format "date-time"))}} + {{- if isDateOrDateTimeGenerated $payload.Format }} t, err := time.Parse(time.RFC3339, string(bMsg.Payload)) if err != nil { return {{namify .Name}}{}, err @@ -124,7 +124,7 @@ func brokerMessageTo{{namify .Name}}(bMsg extensions.BrokerMessage) ({{namify .N if err != nil { return msg, err } - {{- else if or (eq $value.Format "date") (eq $value.Format "date-time")}} + {{- else if isDateOrDateTimeGenerated $value.Format }} t, err := time.Parse(time.RFC3339, string(v)) if err != nil { return msg, err @@ -143,7 +143,7 @@ func brokerMessageTo{{namify .Name}}(bMsg extensions.BrokerMessage) ({{namify .N if err != nil { return msg, err } - {{- else if or (eq $value.Format "date") (eq $value.Format "date-time")}} + {{- else if isDateOrDateTimeGenerated $value.Format }} t, err := time.Parse(time.RFC3339, string(v)) if err != nil { return msg, err @@ -205,7 +205,7 @@ func (msg {{namify .Name}}) toBrokerMessage() (extensions.BrokerMessage, error) payload := make([]byte, 8) binary.BigEndian.PutUint64(payload, math.Float64bits(msg.Payload)) {{- end}} - {{- else if and (eq $payload.Type "string") (and $payload.Format (or (eq $payload.Format "date") (eq $payload.Format "date-time")))}} + {{- else if and (eq $payload.Type "string") (isDateOrDateTimeGenerated $payload.Format) }} // Convert to RFC3339 and to []byte payload := []byte(msg.Payload.Format(time.RFC3339)) {{- else}} @@ -235,7 +235,7 @@ func (msg {{namify .Name}}) toBrokerMessage() (extensions.BrokerMessage, error) return extensions.BrokerMessage{}, err } headers["{{$key}}"] = h - {{- else if or (eq $value.Format "date") (eq $value.Format "date-time")}} + {{- else if isDateOrDateTimeGenerated $value.Format }} headers["{{$key}}"] = []byte(msg.Headers.{{namify $key}}.Format(time.RFC3339)) {{- else }} headers["{{$key}}"] = []byte(msg.Headers.{{namify $key}}) @@ -248,7 +248,7 @@ func (msg {{namify .Name}}) toBrokerMessage() (extensions.BrokerMessage, error) return extensions.BrokerMessage{}, err } headers["{{$key}}"] = h - {{- else if or (eq $value.Format "date") (eq $value.Format "date-time")}} + {{- else if isDateOrDateTimeGenerated $value.Format }} headers["{{$key}}"] = []byte(msg.Headers.{{namify $key}}.Format(time.RFC3339)) {{- else }} headers["{{$key}}"] = []byte(*msg.Headers.{{namify $key}}) diff --git a/pkg/codegen/generators/v3/templates/schema_definition.tmpl b/pkg/codegen/generators/v3/templates/schema_definition.tmpl index 2fac5dda..e37d7c26 100644 --- a/pkg/codegen/generators/v3/templates/schema_definition.tmpl +++ b/pkg/codegen/generators/v3/templates/schema_definition.tmpl @@ -35,7 +35,7 @@ type {{ namify .Name }} struct { type {{ .Name }} {{template "schema-name" .}} {{/* Create specific marshaling for time */ -}} -{{- if or (eq .Format "date") (eq .Format "date-time") -}} +{{- if isDateOrDateTimeGenerated .Format -}} {{template "marshaling-time" .}} {{- end -}} diff --git a/pkg/codegen/generators/v3/templates/schema_name.tmpl b/pkg/codegen/generators/v3/templates/schema_name.tmpl index 81e179ce..03bc71a9 100644 --- a/pkg/codegen/generators/v3/templates/schema_name.tmpl +++ b/pkg/codegen/generators/v3/templates/schema_name.tmpl @@ -16,9 +16,9 @@ bool {{- /* --------------------------- Type String -------------------------- */ -}} {{- else if eq .Type "string" -}} -{{- if and .Format (eq .Format "date") -}} +{{- if and (isDateOrDateTimeGenerated .Format) (eq .Format "date") -}} civil.Date -{{- else if and .Format (eq .Format "date-time") -}} +{{- else if and (isDateOrDateTimeGenerated .Format) (eq .Format "date-time") -}} time.Time {{- else -}} string diff --git a/pkg/codegen/options/options.go b/pkg/codegen/options/options.go index 3056ab41..c44466b6 100644 --- a/pkg/codegen/options/options.go +++ b/pkg/codegen/options/options.go @@ -32,4 +32,7 @@ type Options struct { // NamingScheme defines the naming scheme for generated golang structs // Supported values: camel, none NamingScheme string + + // IgnoreStringFormat states whether the properties' format (date, date-time) should impact the type in types + IgnoreStringFormat bool } diff --git a/pkg/utils/template/helpers.go b/pkg/utils/template/helpers.go index 7bfbb6c2..b9553c5d 100644 --- a/pkg/utils/template/helpers.go +++ b/pkg/utils/template/helpers.go @@ -144,18 +144,28 @@ func CutSuffix(s, suffix string) string { return s } +var isDateOrDateTimeGenerated = func(format string) bool { + return format == "date" || format == "date-time" +} + +// DisableDateOrTimeGeneration is used to disable the generation of date/date-time formats within types. +func DisableDateOrTimeGeneration() { + isDateOrDateTimeGenerated = func(_ string) bool { return false } +} + // HelpersFunctions returns the functions that can be used as helpers // in a golang template. func HelpersFunctions() template.FuncMap { return template.FuncMap{ - "namifyWithoutParam": NamifyWithoutParams, - "namify": Namify, - "convertKey": ConvertKey, - "snakeCase": strcase.ToSnake, - "hasField": HasField, - "describeStruct": DescribeStruct, - "multiLineComment": MultiLineComment, - "cutSuffix": CutSuffix, - "args": Args, + "namifyWithoutParam": NamifyWithoutParams, + "namify": Namify, + "isDateOrDateTimeGenerated": isDateOrDateTimeGenerated, + "convertKey": ConvertKey, + "snakeCase": strcase.ToSnake, + "hasField": HasField, + "describeStruct": DescribeStruct, + "multiLineComment": MultiLineComment, + "cutSuffix": CutSuffix, + "args": Args, } } diff --git a/test/v2/issues/255/asyncapi.yaml b/test/v2/issues/255/asyncapi.yaml new file mode 100644 index 00000000..4b143f05 --- /dev/null +++ b/test/v2/issues/255/asyncapi.yaml @@ -0,0 +1,17 @@ +asyncapi: 2.6.0 +info: + title: Sample App + version: 1.2.3 + +components: + messages: + Test: + payload: + type: object + properties: + DateProp: + type: string + format: date + DateTimeProp: + type: string + format: date-time \ No newline at end of file diff --git a/test/v2/issues/255/default/asyncapi.gen.go b/test/v2/issues/255/default/asyncapi.gen.go new file mode 100644 index 00000000..4ab0a48c --- /dev/null +++ b/test/v2/issues/255/default/asyncapi.gen.go @@ -0,0 +1,321 @@ +// Package "issue255default" provides primitives to interact with the AsyncAPI specification. +// +// Code generated by github.com/lerenn/asyncapi-codegen version (devel) DO NOT EDIT. +package issue255default + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/lerenn/asyncapi-codegen/pkg/extensions" + + "cloud.google.com/go/civil" +) + +// AppController is the structure that provides publishing capabilities to the +// developer and and connect the broker with the App +type AppController struct { + controller +} + +// NewAppController links the App to the broker +func NewAppController(bc extensions.BrokerController, options ...ControllerOption) (*AppController, error) { + // Check if broker controller has been provided + if bc == nil { + return nil, extensions.ErrNilBrokerController + } + + // Create default controller + controller := controller{ + broker: bc, + subscriptions: make(map[string]extensions.BrokerChannelSubscription), + logger: extensions.DummyLogger{}, + middlewares: make([]extensions.Middleware, 0), + errorHandler: extensions.DefaultErrorHandler(), + } + + // Apply options + for _, option := range options { + option(&controller) + } + + return &AppController{controller: controller}, nil +} + +func (c AppController) wrapMiddlewares( + middlewares []extensions.Middleware, + callback extensions.NextMiddleware, +) func(ctx context.Context, msg *extensions.BrokerMessage) error { + var called bool + + // If there is no more middleware + if len(middlewares) == 0 { + return func(ctx context.Context, msg *extensions.BrokerMessage) error { + // Call the callback if it exists and it has not been called already + if callback != nil && !called { + called = true + return callback(ctx) + } + + // Nil can be returned, as the callback has already been called + return nil + } + } + + // Get the next function to call from next middlewares or callback + next := c.wrapMiddlewares(middlewares[1:], callback) + + // Wrap middleware into a check function that will call execute the middleware + // and call the next wrapped middleware if the returned function has not been + // called already + return func(ctx context.Context, msg *extensions.BrokerMessage) error { + // Call the middleware and the following if it has not been done already + if !called { + // Create the next call with the context and the message + nextWithArgs := func(ctx context.Context) error { + return next(ctx, msg) + } + + // Call the middleware and register it as already called + called = true + if err := middlewares[0](ctx, msg, nextWithArgs); err != nil { + return err + } + + // If next has already been called in middleware, it should not be executed again + return nextWithArgs(ctx) + } + + // Nil can be returned, as the next middleware has already been called + return nil + } +} + +func (c AppController) executeMiddlewares(ctx context.Context, msg *extensions.BrokerMessage, callback extensions.NextMiddleware) error { + // Wrap middleware to have 'next' function when calling them + wrapped := c.wrapMiddlewares(c.middlewares, callback) + + // Execute wrapped middlewares + return wrapped(ctx, msg) +} + +func addAppContextValues(ctx context.Context, path string) context.Context { + ctx = context.WithValue(ctx, extensions.ContextKeyIsVersion, "1.2.3") + ctx = context.WithValue(ctx, extensions.ContextKeyIsProvider, "app") + return context.WithValue(ctx, extensions.ContextKeyIsChannel, path) +} + +// Close will clean up any existing resources on the controller +func (c *AppController) Close(ctx context.Context) { + // Unsubscribing remaining channels +} + +// UserController is the structure that provides publishing capabilities to the +// developer and and connect the broker with the User +type UserController struct { + controller +} + +// NewUserController links the User to the broker +func NewUserController(bc extensions.BrokerController, options ...ControllerOption) (*UserController, error) { + // Check if broker controller has been provided + if bc == nil { + return nil, extensions.ErrNilBrokerController + } + + // Create default controller + controller := controller{ + broker: bc, + subscriptions: make(map[string]extensions.BrokerChannelSubscription), + logger: extensions.DummyLogger{}, + middlewares: make([]extensions.Middleware, 0), + errorHandler: extensions.DefaultErrorHandler(), + } + + // Apply options + for _, option := range options { + option(&controller) + } + + return &UserController{controller: controller}, nil +} + +func (c UserController) wrapMiddlewares( + middlewares []extensions.Middleware, + callback extensions.NextMiddleware, +) func(ctx context.Context, msg *extensions.BrokerMessage) error { + var called bool + + // If there is no more middleware + if len(middlewares) == 0 { + return func(ctx context.Context, msg *extensions.BrokerMessage) error { + // Call the callback if it exists and it has not been called already + if callback != nil && !called { + called = true + return callback(ctx) + } + + // Nil can be returned, as the callback has already been called + return nil + } + } + + // Get the next function to call from next middlewares or callback + next := c.wrapMiddlewares(middlewares[1:], callback) + + // Wrap middleware into a check function that will call execute the middleware + // and call the next wrapped middleware if the returned function has not been + // called already + return func(ctx context.Context, msg *extensions.BrokerMessage) error { + // Call the middleware and the following if it has not been done already + if !called { + // Create the next call with the context and the message + nextWithArgs := func(ctx context.Context) error { + return next(ctx, msg) + } + + // Call the middleware and register it as already called + called = true + if err := middlewares[0](ctx, msg, nextWithArgs); err != nil { + return err + } + + // If next has already been called in middleware, it should not be executed again + return nextWithArgs(ctx) + } + + // Nil can be returned, as the next middleware has already been called + return nil + } +} + +func (c UserController) executeMiddlewares(ctx context.Context, msg *extensions.BrokerMessage, callback extensions.NextMiddleware) error { + // Wrap middleware to have 'next' function when calling them + wrapped := c.wrapMiddlewares(c.middlewares, callback) + + // Execute wrapped middlewares + return wrapped(ctx, msg) +} + +func addUserContextValues(ctx context.Context, path string) context.Context { + ctx = context.WithValue(ctx, extensions.ContextKeyIsVersion, "1.2.3") + ctx = context.WithValue(ctx, extensions.ContextKeyIsProvider, "user") + return context.WithValue(ctx, extensions.ContextKeyIsChannel, path) +} + +// Close will clean up any existing resources on the controller +func (c *UserController) Close(ctx context.Context) { + // Unsubscribing remaining channels +} + +// AsyncAPIVersion is the version of the used AsyncAPI document +const AsyncAPIVersion = "1.2.3" + +// controller is the controller that will be used to communicate with the broker +// It will be used internally by AppController and UserController +type controller struct { + // broker is the broker controller that will be used to communicate + broker extensions.BrokerController + // subscriptions is a map of all subscriptions + subscriptions map[string]extensions.BrokerChannelSubscription + // logger is the logger that will be used² to log operations on controller + logger extensions.Logger + // middlewares are the middlewares that will be executed when sending or + // receiving messages + middlewares []extensions.Middleware + // handler to handle errors from consumers and middlewares + errorHandler extensions.ErrorHandler +} + +// ControllerOption is the type of the options that can be passed +// when creating a new Controller +type ControllerOption func(controller *controller) + +// WithLogger attaches a logger to the controller +func WithLogger(logger extensions.Logger) ControllerOption { + return func(controller *controller) { + controller.logger = logger + } +} + +// WithMiddlewares attaches middlewares that will be executed when sending or receiving messages +func WithMiddlewares(middlewares ...extensions.Middleware) ControllerOption { + return func(controller *controller) { + controller.middlewares = middlewares + } +} + +// WithErrorHandler attaches a errorhandler to handle errors from subscriber functions +func WithErrorHandler(handler extensions.ErrorHandler) ControllerOption { + return func(controller *controller) { + controller.errorHandler = handler + } +} + +type MessageWithCorrelationID interface { + CorrelationID() string + SetCorrelationID(id string) +} + +type Error struct { + Channel string + Err error +} + +func (e *Error) Error() string { + return fmt.Sprintf("channel %q: err %v", e.Channel, e.Err) +} + +// TestMessagePayload is a schema from the AsyncAPI specification required in messages +type TestMessagePayload struct { + DateProp *civil.Date `json:"DateProp,omitempty"` + DateTimeProp *time.Time `json:"DateTimeProp,omitempty"` +} + +// TestMessage is the message expected for 'TestMessage' channel. +type TestMessage struct { + // Payload will be inserted in the message payload + Payload TestMessagePayload +} + +func NewTestMessage() TestMessage { + var msg TestMessage + + return msg +} + +// brokerMessageToTestMessage will fill a new TestMessage with data from generic broker message +func brokerMessageToTestMessage(bMsg extensions.BrokerMessage) (TestMessage, error) { + var msg TestMessage + + // Unmarshal payload to expected message payload format + err := json.Unmarshal(bMsg.Payload, &msg.Payload) + if err != nil { + return msg, err + } + + // TODO: run checks on msg type + + return msg, nil +} + +// toBrokerMessage will generate a generic broker message from TestMessage data +func (msg TestMessage) toBrokerMessage() (extensions.BrokerMessage, error) { + // TODO: implement checks on message + + // Marshal payload to JSON + payload, err := json.Marshal(msg.Payload) + if err != nil { + return extensions.BrokerMessage{}, err + } + + // There is no headers here + headers := make(map[string][]byte, 0) + + return extensions.BrokerMessage{ + Headers: headers, + Payload: payload, + }, nil +} diff --git a/test/v2/issues/255/ignoredates/asyncapi.gen.go b/test/v2/issues/255/ignoredates/asyncapi.gen.go new file mode 100644 index 00000000..173790b8 --- /dev/null +++ b/test/v2/issues/255/ignoredates/asyncapi.gen.go @@ -0,0 +1,318 @@ +// Package "issue255ignoredates" provides primitives to interact with the AsyncAPI specification. +// +// Code generated by github.com/lerenn/asyncapi-codegen version (devel) DO NOT EDIT. +package issue255ignoredates + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/lerenn/asyncapi-codegen/pkg/extensions" +) + +// AppController is the structure that provides publishing capabilities to the +// developer and and connect the broker with the App +type AppController struct { + controller +} + +// NewAppController links the App to the broker +func NewAppController(bc extensions.BrokerController, options ...ControllerOption) (*AppController, error) { + // Check if broker controller has been provided + if bc == nil { + return nil, extensions.ErrNilBrokerController + } + + // Create default controller + controller := controller{ + broker: bc, + subscriptions: make(map[string]extensions.BrokerChannelSubscription), + logger: extensions.DummyLogger{}, + middlewares: make([]extensions.Middleware, 0), + errorHandler: extensions.DefaultErrorHandler(), + } + + // Apply options + for _, option := range options { + option(&controller) + } + + return &AppController{controller: controller}, nil +} + +func (c AppController) wrapMiddlewares( + middlewares []extensions.Middleware, + callback extensions.NextMiddleware, +) func(ctx context.Context, msg *extensions.BrokerMessage) error { + var called bool + + // If there is no more middleware + if len(middlewares) == 0 { + return func(ctx context.Context, msg *extensions.BrokerMessage) error { + // Call the callback if it exists and it has not been called already + if callback != nil && !called { + called = true + return callback(ctx) + } + + // Nil can be returned, as the callback has already been called + return nil + } + } + + // Get the next function to call from next middlewares or callback + next := c.wrapMiddlewares(middlewares[1:], callback) + + // Wrap middleware into a check function that will call execute the middleware + // and call the next wrapped middleware if the returned function has not been + // called already + return func(ctx context.Context, msg *extensions.BrokerMessage) error { + // Call the middleware and the following if it has not been done already + if !called { + // Create the next call with the context and the message + nextWithArgs := func(ctx context.Context) error { + return next(ctx, msg) + } + + // Call the middleware and register it as already called + called = true + if err := middlewares[0](ctx, msg, nextWithArgs); err != nil { + return err + } + + // If next has already been called in middleware, it should not be executed again + return nextWithArgs(ctx) + } + + // Nil can be returned, as the next middleware has already been called + return nil + } +} + +func (c AppController) executeMiddlewares(ctx context.Context, msg *extensions.BrokerMessage, callback extensions.NextMiddleware) error { + // Wrap middleware to have 'next' function when calling them + wrapped := c.wrapMiddlewares(c.middlewares, callback) + + // Execute wrapped middlewares + return wrapped(ctx, msg) +} + +func addAppContextValues(ctx context.Context, path string) context.Context { + ctx = context.WithValue(ctx, extensions.ContextKeyIsVersion, "1.2.3") + ctx = context.WithValue(ctx, extensions.ContextKeyIsProvider, "app") + return context.WithValue(ctx, extensions.ContextKeyIsChannel, path) +} + +// Close will clean up any existing resources on the controller +func (c *AppController) Close(ctx context.Context) { + // Unsubscribing remaining channels +} + +// UserController is the structure that provides publishing capabilities to the +// developer and and connect the broker with the User +type UserController struct { + controller +} + +// NewUserController links the User to the broker +func NewUserController(bc extensions.BrokerController, options ...ControllerOption) (*UserController, error) { + // Check if broker controller has been provided + if bc == nil { + return nil, extensions.ErrNilBrokerController + } + + // Create default controller + controller := controller{ + broker: bc, + subscriptions: make(map[string]extensions.BrokerChannelSubscription), + logger: extensions.DummyLogger{}, + middlewares: make([]extensions.Middleware, 0), + errorHandler: extensions.DefaultErrorHandler(), + } + + // Apply options + for _, option := range options { + option(&controller) + } + + return &UserController{controller: controller}, nil +} + +func (c UserController) wrapMiddlewares( + middlewares []extensions.Middleware, + callback extensions.NextMiddleware, +) func(ctx context.Context, msg *extensions.BrokerMessage) error { + var called bool + + // If there is no more middleware + if len(middlewares) == 0 { + return func(ctx context.Context, msg *extensions.BrokerMessage) error { + // Call the callback if it exists and it has not been called already + if callback != nil && !called { + called = true + return callback(ctx) + } + + // Nil can be returned, as the callback has already been called + return nil + } + } + + // Get the next function to call from next middlewares or callback + next := c.wrapMiddlewares(middlewares[1:], callback) + + // Wrap middleware into a check function that will call execute the middleware + // and call the next wrapped middleware if the returned function has not been + // called already + return func(ctx context.Context, msg *extensions.BrokerMessage) error { + // Call the middleware and the following if it has not been done already + if !called { + // Create the next call with the context and the message + nextWithArgs := func(ctx context.Context) error { + return next(ctx, msg) + } + + // Call the middleware and register it as already called + called = true + if err := middlewares[0](ctx, msg, nextWithArgs); err != nil { + return err + } + + // If next has already been called in middleware, it should not be executed again + return nextWithArgs(ctx) + } + + // Nil can be returned, as the next middleware has already been called + return nil + } +} + +func (c UserController) executeMiddlewares(ctx context.Context, msg *extensions.BrokerMessage, callback extensions.NextMiddleware) error { + // Wrap middleware to have 'next' function when calling them + wrapped := c.wrapMiddlewares(c.middlewares, callback) + + // Execute wrapped middlewares + return wrapped(ctx, msg) +} + +func addUserContextValues(ctx context.Context, path string) context.Context { + ctx = context.WithValue(ctx, extensions.ContextKeyIsVersion, "1.2.3") + ctx = context.WithValue(ctx, extensions.ContextKeyIsProvider, "user") + return context.WithValue(ctx, extensions.ContextKeyIsChannel, path) +} + +// Close will clean up any existing resources on the controller +func (c *UserController) Close(ctx context.Context) { + // Unsubscribing remaining channels +} + +// AsyncAPIVersion is the version of the used AsyncAPI document +const AsyncAPIVersion = "1.2.3" + +// controller is the controller that will be used to communicate with the broker +// It will be used internally by AppController and UserController +type controller struct { + // broker is the broker controller that will be used to communicate + broker extensions.BrokerController + // subscriptions is a map of all subscriptions + subscriptions map[string]extensions.BrokerChannelSubscription + // logger is the logger that will be used² to log operations on controller + logger extensions.Logger + // middlewares are the middlewares that will be executed when sending or + // receiving messages + middlewares []extensions.Middleware + // handler to handle errors from consumers and middlewares + errorHandler extensions.ErrorHandler +} + +// ControllerOption is the type of the options that can be passed +// when creating a new Controller +type ControllerOption func(controller *controller) + +// WithLogger attaches a logger to the controller +func WithLogger(logger extensions.Logger) ControllerOption { + return func(controller *controller) { + controller.logger = logger + } +} + +// WithMiddlewares attaches middlewares that will be executed when sending or receiving messages +func WithMiddlewares(middlewares ...extensions.Middleware) ControllerOption { + return func(controller *controller) { + controller.middlewares = middlewares + } +} + +// WithErrorHandler attaches a errorhandler to handle errors from subscriber functions +func WithErrorHandler(handler extensions.ErrorHandler) ControllerOption { + return func(controller *controller) { + controller.errorHandler = handler + } +} + +type MessageWithCorrelationID interface { + CorrelationID() string + SetCorrelationID(id string) +} + +type Error struct { + Channel string + Err error +} + +func (e *Error) Error() string { + return fmt.Sprintf("channel %q: err %v", e.Channel, e.Err) +} + +// TestMessagePayload is a schema from the AsyncAPI specification required in messages +type TestMessagePayload struct { + DateProp *string `json:"DateProp,omitempty"` + DateTimeProp *string `json:"DateTimeProp,omitempty"` +} + +// TestMessage is the message expected for 'TestMessage' channel. +type TestMessage struct { + // Payload will be inserted in the message payload + Payload TestMessagePayload +} + +func NewTestMessage() TestMessage { + var msg TestMessage + + return msg +} + +// brokerMessageToTestMessage will fill a new TestMessage with data from generic broker message +func brokerMessageToTestMessage(bMsg extensions.BrokerMessage) (TestMessage, error) { + var msg TestMessage + + // Unmarshal payload to expected message payload format + err := json.Unmarshal(bMsg.Payload, &msg.Payload) + if err != nil { + return msg, err + } + + // TODO: run checks on msg type + + return msg, nil +} + +// toBrokerMessage will generate a generic broker message from TestMessage data +func (msg TestMessage) toBrokerMessage() (extensions.BrokerMessage, error) { + // TODO: implement checks on message + + // Marshal payload to JSON + payload, err := json.Marshal(msg.Payload) + if err != nil { + return extensions.BrokerMessage{}, err + } + + // There is no headers here + headers := make(map[string][]byte, 0) + + return extensions.BrokerMessage{ + Headers: headers, + Payload: payload, + }, nil +} diff --git a/test/v2/issues/255/suite_test.go b/test/v2/issues/255/suite_test.go new file mode 100644 index 00000000..8699df58 --- /dev/null +++ b/test/v2/issues/255/suite_test.go @@ -0,0 +1,44 @@ +//go:generate go run ../../../../cmd/asyncapi-codegen --ignore-string-format -p issue255ignoredates -i ./asyncapi.yaml -o ./ignoredates/asyncapi.gen.go +//go:generate go run ../../../../cmd/asyncapi-codegen -p issue255default -i ./asyncapi.yaml -o ./default/asyncapi.gen.go + +package issue255 + +import ( + "testing" + "time" + + "cloud.google.com/go/civil" + issue255default "github.com/lerenn/asyncapi-codegen/test/v2/issues/255/default" + issue255ignoredates "github.com/lerenn/asyncapi-codegen/test/v2/issues/255/ignoredates" + "github.com/stretchr/testify/suite" +) + +func TestSuite(t *testing.T) { + suite.Run(t, NewSuite()) +} + +type Suite struct { + suite.Suite +} + +func NewSuite() *Suite { + return &Suite{} +} + +func (suite *Suite) TestDefault() { + t := time.Now() + _ = issue255default.TestMessagePayload{ + DateProp: &civil.Date{ + Year: 2024, + Month: 12, + Day: 12, + }, + DateTimeProp: &t, + } + + s := "toto" + _ = issue255ignoredates.TestMessagePayload{ + DateProp: &s, + DateTimeProp: &s, + } +} diff --git a/test/v3/issues/255/asyncapi.yaml b/test/v3/issues/255/asyncapi.yaml new file mode 100644 index 00000000..32052251 --- /dev/null +++ b/test/v3/issues/255/asyncapi.yaml @@ -0,0 +1,17 @@ +asyncapi: 3.0.0 +info: + title: Sample App + version: 1.2.3 + +components: + messages: + Test: + payload: + type: object + properties: + DateProp: + type: string + format: date + DateTimeProp: + type: string + format: date-time \ No newline at end of file diff --git a/test/v3/issues/255/default/asyncapi.gen.go b/test/v3/issues/255/default/asyncapi.gen.go new file mode 100644 index 00000000..cb98a1b0 --- /dev/null +++ b/test/v3/issues/255/default/asyncapi.gen.go @@ -0,0 +1,321 @@ +// Package "issue255default" provides primitives to interact with the AsyncAPI specification. +// +// Code generated by github.com/lerenn/asyncapi-codegen version (devel) DO NOT EDIT. +package issue255default + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/lerenn/asyncapi-codegen/pkg/extensions" + + "cloud.google.com/go/civil" +) + +// AppController is the structure that provides sending capabilities to the +// developer and and connect the broker with the App +type AppController struct { + controller +} + +// NewAppController links the App to the broker +func NewAppController(bc extensions.BrokerController, options ...ControllerOption) (*AppController, error) { + // Check if broker controller has been provided + if bc == nil { + return nil, extensions.ErrNilBrokerController + } + + // Create default controller + controller := controller{ + broker: bc, + subscriptions: make(map[string]extensions.BrokerChannelSubscription), + logger: extensions.DummyLogger{}, + middlewares: make([]extensions.Middleware, 0), + errorHandler: extensions.DefaultErrorHandler(), + } + + // Apply options + for _, option := range options { + option(&controller) + } + + return &AppController{controller: controller}, nil +} + +func (c AppController) wrapMiddlewares( + middlewares []extensions.Middleware, + callback extensions.NextMiddleware, +) func(ctx context.Context, msg *extensions.BrokerMessage) error { + var called bool + + // If there is no more middleware + if len(middlewares) == 0 { + return func(ctx context.Context, msg *extensions.BrokerMessage) error { + // Call the callback if it exists and it has not been called already + if callback != nil && !called { + called = true + return callback(ctx) + } + + // Nil can be returned, as the callback has already been called + return nil + } + } + + // Get the next function to call from next middlewares or callback + next := c.wrapMiddlewares(middlewares[1:], callback) + + // Wrap middleware into a check function that will call execute the middleware + // and call the next wrapped middleware if the returned function has not been + // called already + return func(ctx context.Context, msg *extensions.BrokerMessage) error { + // Call the middleware and the following if it has not been done already + if !called { + // Create the next call with the context and the message + nextWithArgs := func(ctx context.Context) error { + return next(ctx, msg) + } + + // Call the middleware and register it as already called + called = true + if err := middlewares[0](ctx, msg, nextWithArgs); err != nil { + return err + } + + // If next has already been called in middleware, it should not be executed again + return nextWithArgs(ctx) + } + + // Nil can be returned, as the next middleware has already been called + return nil + } +} + +func (c AppController) executeMiddlewares(ctx context.Context, msg *extensions.BrokerMessage, callback extensions.NextMiddleware) error { + // Wrap middleware to have 'next' function when calling them + wrapped := c.wrapMiddlewares(c.middlewares, callback) + + // Execute wrapped middlewares + return wrapped(ctx, msg) +} + +func addAppContextValues(ctx context.Context, addr string) context.Context { + ctx = context.WithValue(ctx, extensions.ContextKeyIsVersion, "1.2.3") + ctx = context.WithValue(ctx, extensions.ContextKeyIsProvider, "app") + return context.WithValue(ctx, extensions.ContextKeyIsChannel, addr) +} + +// Close will clean up any existing resources on the controller +func (c *AppController) Close(ctx context.Context) { + // Unsubscribing remaining channels +} + +// UserController is the structure that provides sending capabilities to the +// developer and and connect the broker with the User +type UserController struct { + controller +} + +// NewUserController links the User to the broker +func NewUserController(bc extensions.BrokerController, options ...ControllerOption) (*UserController, error) { + // Check if broker controller has been provided + if bc == nil { + return nil, extensions.ErrNilBrokerController + } + + // Create default controller + controller := controller{ + broker: bc, + subscriptions: make(map[string]extensions.BrokerChannelSubscription), + logger: extensions.DummyLogger{}, + middlewares: make([]extensions.Middleware, 0), + errorHandler: extensions.DefaultErrorHandler(), + } + + // Apply options + for _, option := range options { + option(&controller) + } + + return &UserController{controller: controller}, nil +} + +func (c UserController) wrapMiddlewares( + middlewares []extensions.Middleware, + callback extensions.NextMiddleware, +) func(ctx context.Context, msg *extensions.BrokerMessage) error { + var called bool + + // If there is no more middleware + if len(middlewares) == 0 { + return func(ctx context.Context, msg *extensions.BrokerMessage) error { + // Call the callback if it exists and it has not been called already + if callback != nil && !called { + called = true + return callback(ctx) + } + + // Nil can be returned, as the callback has already been called + return nil + } + } + + // Get the next function to call from next middlewares or callback + next := c.wrapMiddlewares(middlewares[1:], callback) + + // Wrap middleware into a check function that will call execute the middleware + // and call the next wrapped middleware if the returned function has not been + // called already + return func(ctx context.Context, msg *extensions.BrokerMessage) error { + // Call the middleware and the following if it has not been done already + if !called { + // Create the next call with the context and the message + nextWithArgs := func(ctx context.Context) error { + return next(ctx, msg) + } + + // Call the middleware and register it as already called + called = true + if err := middlewares[0](ctx, msg, nextWithArgs); err != nil { + return err + } + + // If next has already been called in middleware, it should not be executed again + return nextWithArgs(ctx) + } + + // Nil can be returned, as the next middleware has already been called + return nil + } +} + +func (c UserController) executeMiddlewares(ctx context.Context, msg *extensions.BrokerMessage, callback extensions.NextMiddleware) error { + // Wrap middleware to have 'next' function when calling them + wrapped := c.wrapMiddlewares(c.middlewares, callback) + + // Execute wrapped middlewares + return wrapped(ctx, msg) +} + +func addUserContextValues(ctx context.Context, addr string) context.Context { + ctx = context.WithValue(ctx, extensions.ContextKeyIsVersion, "1.2.3") + ctx = context.WithValue(ctx, extensions.ContextKeyIsProvider, "user") + return context.WithValue(ctx, extensions.ContextKeyIsChannel, addr) +} + +// Close will clean up any existing resources on the controller +func (c *UserController) Close(ctx context.Context) { + // Unsubscribing remaining channels +} + +// AsyncAPIVersion is the version of the used AsyncAPI document +const AsyncAPIVersion = "1.2.3" + +// controller is the controller that will be used to communicate with the broker +// It will be used internally by AppController and UserController +type controller struct { + // broker is the broker controller that will be used to communicate + broker extensions.BrokerController + // subscriptions is a map of all subscriptions + subscriptions map[string]extensions.BrokerChannelSubscription + // logger is the logger that will be used² to log operations on controller + logger extensions.Logger + // middlewares are the middlewares that will be executed when sending or + // receiving messages + middlewares []extensions.Middleware + // handler to handle errors from consumers and middlewares + errorHandler extensions.ErrorHandler +} + +// ControllerOption is the type of the options that can be passed +// when creating a new Controller +type ControllerOption func(controller *controller) + +// WithLogger attaches a logger to the controller +func WithLogger(logger extensions.Logger) ControllerOption { + return func(controller *controller) { + controller.logger = logger + } +} + +// WithMiddlewares attaches middlewares that will be executed when sending or receiving messages +func WithMiddlewares(middlewares ...extensions.Middleware) ControllerOption { + return func(controller *controller) { + controller.middlewares = middlewares + } +} + +// WithErrorHandler attaches a errorhandler to handle errors from subscriber functions +func WithErrorHandler(handler extensions.ErrorHandler) ControllerOption { + return func(controller *controller) { + controller.errorHandler = handler + } +} + +type MessageWithCorrelationID interface { + CorrelationID() string + SetCorrelationID(id string) +} + +type Error struct { + Channel string + Err error +} + +func (e *Error) Error() string { + return fmt.Sprintf("channel %q: err %v", e.Channel, e.Err) +} + +// TestMessagePayload is a schema from the AsyncAPI specification required in messages +type TestMessagePayload struct { + DateProp *civil.Date `json:"DateProp,omitempty"` + DateTimeProp *time.Time `json:"DateTimeProp,omitempty"` +} + +// TestMessage is the message expected for 'TestMessage' channel. +type TestMessage struct { + // Payload will be inserted in the message payload + Payload TestMessagePayload +} + +func NewTestMessage() TestMessage { + var msg TestMessage + + return msg +} + +// brokerMessageToTestMessage will fill a new TestMessage with data from generic broker message +func brokerMessageToTestMessage(bMsg extensions.BrokerMessage) (TestMessage, error) { + var msg TestMessage + + // Unmarshal payload to expected message payload format + err := json.Unmarshal(bMsg.Payload, &msg.Payload) + if err != nil { + return msg, err + } + + // TODO: run checks on msg type + + return msg, nil +} + +// toBrokerMessage will generate a generic broker message from TestMessage data +func (msg TestMessage) toBrokerMessage() (extensions.BrokerMessage, error) { + // TODO: implement checks on message + + // Marshal payload to JSON + payload, err := json.Marshal(msg.Payload) + if err != nil { + return extensions.BrokerMessage{}, err + } + + // There is no headers here + headers := make(map[string][]byte, 0) + + return extensions.BrokerMessage{ + Headers: headers, + Payload: payload, + }, nil +} diff --git a/test/v3/issues/255/ignoredates/asyncapi.gen.go b/test/v3/issues/255/ignoredates/asyncapi.gen.go new file mode 100644 index 00000000..ab4e1bb7 --- /dev/null +++ b/test/v3/issues/255/ignoredates/asyncapi.gen.go @@ -0,0 +1,318 @@ +// Package "issue255ignoredates" provides primitives to interact with the AsyncAPI specification. +// +// Code generated by github.com/lerenn/asyncapi-codegen version (devel) DO NOT EDIT. +package issue255ignoredates + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/lerenn/asyncapi-codegen/pkg/extensions" +) + +// AppController is the structure that provides sending capabilities to the +// developer and and connect the broker with the App +type AppController struct { + controller +} + +// NewAppController links the App to the broker +func NewAppController(bc extensions.BrokerController, options ...ControllerOption) (*AppController, error) { + // Check if broker controller has been provided + if bc == nil { + return nil, extensions.ErrNilBrokerController + } + + // Create default controller + controller := controller{ + broker: bc, + subscriptions: make(map[string]extensions.BrokerChannelSubscription), + logger: extensions.DummyLogger{}, + middlewares: make([]extensions.Middleware, 0), + errorHandler: extensions.DefaultErrorHandler(), + } + + // Apply options + for _, option := range options { + option(&controller) + } + + return &AppController{controller: controller}, nil +} + +func (c AppController) wrapMiddlewares( + middlewares []extensions.Middleware, + callback extensions.NextMiddleware, +) func(ctx context.Context, msg *extensions.BrokerMessage) error { + var called bool + + // If there is no more middleware + if len(middlewares) == 0 { + return func(ctx context.Context, msg *extensions.BrokerMessage) error { + // Call the callback if it exists and it has not been called already + if callback != nil && !called { + called = true + return callback(ctx) + } + + // Nil can be returned, as the callback has already been called + return nil + } + } + + // Get the next function to call from next middlewares or callback + next := c.wrapMiddlewares(middlewares[1:], callback) + + // Wrap middleware into a check function that will call execute the middleware + // and call the next wrapped middleware if the returned function has not been + // called already + return func(ctx context.Context, msg *extensions.BrokerMessage) error { + // Call the middleware and the following if it has not been done already + if !called { + // Create the next call with the context and the message + nextWithArgs := func(ctx context.Context) error { + return next(ctx, msg) + } + + // Call the middleware and register it as already called + called = true + if err := middlewares[0](ctx, msg, nextWithArgs); err != nil { + return err + } + + // If next has already been called in middleware, it should not be executed again + return nextWithArgs(ctx) + } + + // Nil can be returned, as the next middleware has already been called + return nil + } +} + +func (c AppController) executeMiddlewares(ctx context.Context, msg *extensions.BrokerMessage, callback extensions.NextMiddleware) error { + // Wrap middleware to have 'next' function when calling them + wrapped := c.wrapMiddlewares(c.middlewares, callback) + + // Execute wrapped middlewares + return wrapped(ctx, msg) +} + +func addAppContextValues(ctx context.Context, addr string) context.Context { + ctx = context.WithValue(ctx, extensions.ContextKeyIsVersion, "1.2.3") + ctx = context.WithValue(ctx, extensions.ContextKeyIsProvider, "app") + return context.WithValue(ctx, extensions.ContextKeyIsChannel, addr) +} + +// Close will clean up any existing resources on the controller +func (c *AppController) Close(ctx context.Context) { + // Unsubscribing remaining channels +} + +// UserController is the structure that provides sending capabilities to the +// developer and and connect the broker with the User +type UserController struct { + controller +} + +// NewUserController links the User to the broker +func NewUserController(bc extensions.BrokerController, options ...ControllerOption) (*UserController, error) { + // Check if broker controller has been provided + if bc == nil { + return nil, extensions.ErrNilBrokerController + } + + // Create default controller + controller := controller{ + broker: bc, + subscriptions: make(map[string]extensions.BrokerChannelSubscription), + logger: extensions.DummyLogger{}, + middlewares: make([]extensions.Middleware, 0), + errorHandler: extensions.DefaultErrorHandler(), + } + + // Apply options + for _, option := range options { + option(&controller) + } + + return &UserController{controller: controller}, nil +} + +func (c UserController) wrapMiddlewares( + middlewares []extensions.Middleware, + callback extensions.NextMiddleware, +) func(ctx context.Context, msg *extensions.BrokerMessage) error { + var called bool + + // If there is no more middleware + if len(middlewares) == 0 { + return func(ctx context.Context, msg *extensions.BrokerMessage) error { + // Call the callback if it exists and it has not been called already + if callback != nil && !called { + called = true + return callback(ctx) + } + + // Nil can be returned, as the callback has already been called + return nil + } + } + + // Get the next function to call from next middlewares or callback + next := c.wrapMiddlewares(middlewares[1:], callback) + + // Wrap middleware into a check function that will call execute the middleware + // and call the next wrapped middleware if the returned function has not been + // called already + return func(ctx context.Context, msg *extensions.BrokerMessage) error { + // Call the middleware and the following if it has not been done already + if !called { + // Create the next call with the context and the message + nextWithArgs := func(ctx context.Context) error { + return next(ctx, msg) + } + + // Call the middleware and register it as already called + called = true + if err := middlewares[0](ctx, msg, nextWithArgs); err != nil { + return err + } + + // If next has already been called in middleware, it should not be executed again + return nextWithArgs(ctx) + } + + // Nil can be returned, as the next middleware has already been called + return nil + } +} + +func (c UserController) executeMiddlewares(ctx context.Context, msg *extensions.BrokerMessage, callback extensions.NextMiddleware) error { + // Wrap middleware to have 'next' function when calling them + wrapped := c.wrapMiddlewares(c.middlewares, callback) + + // Execute wrapped middlewares + return wrapped(ctx, msg) +} + +func addUserContextValues(ctx context.Context, addr string) context.Context { + ctx = context.WithValue(ctx, extensions.ContextKeyIsVersion, "1.2.3") + ctx = context.WithValue(ctx, extensions.ContextKeyIsProvider, "user") + return context.WithValue(ctx, extensions.ContextKeyIsChannel, addr) +} + +// Close will clean up any existing resources on the controller +func (c *UserController) Close(ctx context.Context) { + // Unsubscribing remaining channels +} + +// AsyncAPIVersion is the version of the used AsyncAPI document +const AsyncAPIVersion = "1.2.3" + +// controller is the controller that will be used to communicate with the broker +// It will be used internally by AppController and UserController +type controller struct { + // broker is the broker controller that will be used to communicate + broker extensions.BrokerController + // subscriptions is a map of all subscriptions + subscriptions map[string]extensions.BrokerChannelSubscription + // logger is the logger that will be used² to log operations on controller + logger extensions.Logger + // middlewares are the middlewares that will be executed when sending or + // receiving messages + middlewares []extensions.Middleware + // handler to handle errors from consumers and middlewares + errorHandler extensions.ErrorHandler +} + +// ControllerOption is the type of the options that can be passed +// when creating a new Controller +type ControllerOption func(controller *controller) + +// WithLogger attaches a logger to the controller +func WithLogger(logger extensions.Logger) ControllerOption { + return func(controller *controller) { + controller.logger = logger + } +} + +// WithMiddlewares attaches middlewares that will be executed when sending or receiving messages +func WithMiddlewares(middlewares ...extensions.Middleware) ControllerOption { + return func(controller *controller) { + controller.middlewares = middlewares + } +} + +// WithErrorHandler attaches a errorhandler to handle errors from subscriber functions +func WithErrorHandler(handler extensions.ErrorHandler) ControllerOption { + return func(controller *controller) { + controller.errorHandler = handler + } +} + +type MessageWithCorrelationID interface { + CorrelationID() string + SetCorrelationID(id string) +} + +type Error struct { + Channel string + Err error +} + +func (e *Error) Error() string { + return fmt.Sprintf("channel %q: err %v", e.Channel, e.Err) +} + +// TestMessagePayload is a schema from the AsyncAPI specification required in messages +type TestMessagePayload struct { + DateProp *string `json:"DateProp,omitempty"` + DateTimeProp *string `json:"DateTimeProp,omitempty"` +} + +// TestMessage is the message expected for 'TestMessage' channel. +type TestMessage struct { + // Payload will be inserted in the message payload + Payload TestMessagePayload +} + +func NewTestMessage() TestMessage { + var msg TestMessage + + return msg +} + +// brokerMessageToTestMessage will fill a new TestMessage with data from generic broker message +func brokerMessageToTestMessage(bMsg extensions.BrokerMessage) (TestMessage, error) { + var msg TestMessage + + // Unmarshal payload to expected message payload format + err := json.Unmarshal(bMsg.Payload, &msg.Payload) + if err != nil { + return msg, err + } + + // TODO: run checks on msg type + + return msg, nil +} + +// toBrokerMessage will generate a generic broker message from TestMessage data +func (msg TestMessage) toBrokerMessage() (extensions.BrokerMessage, error) { + // TODO: implement checks on message + + // Marshal payload to JSON + payload, err := json.Marshal(msg.Payload) + if err != nil { + return extensions.BrokerMessage{}, err + } + + // There is no headers here + headers := make(map[string][]byte, 0) + + return extensions.BrokerMessage{ + Headers: headers, + Payload: payload, + }, nil +} diff --git a/test/v3/issues/255/suite_test.go b/test/v3/issues/255/suite_test.go new file mode 100644 index 00000000..044d4166 --- /dev/null +++ b/test/v3/issues/255/suite_test.go @@ -0,0 +1,44 @@ +//go:generate go run ../../../../cmd/asyncapi-codegen --ignore-string-format -p issue255ignoredates -i ./asyncapi.yaml -o ./ignoredates/asyncapi.gen.go +//go:generate go run ../../../../cmd/asyncapi-codegen -p issue255default -i ./asyncapi.yaml -o ./default/asyncapi.gen.go + +package issue255 + +import ( + "testing" + "time" + + "cloud.google.com/go/civil" + issue255default "github.com/lerenn/asyncapi-codegen/test/v2/issues/255/default" + ignoredates "github.com/lerenn/asyncapi-codegen/test/v2/issues/255/ignoredates" + "github.com/stretchr/testify/suite" +) + +func TestSuite(t *testing.T) { + suite.Run(t, NewSuite()) +} + +type Suite struct { + suite.Suite +} + +func NewSuite() *Suite { + return &Suite{} +} + +func (suite *Suite) TestDefault() { + t := time.Now() + _ = issue255default.TestMessagePayload{ + DateProp: &civil.Date{ + Year: 2024, + Month: 12, + Day: 12, + }, + DateTimeProp: &t, + } + + s := "toto" + _ = ignoredates.TestMessagePayload{ + DateProp: &s, + DateTimeProp: &s, + } +}