Skip to content

Commit

Permalink
feat(codegen): Add a flag to force field generation as pointers to ma…
Browse files Browse the repository at this point in the history
…ke validations work (#260)

Co-authored-by: Gildas Lebel <[email protected]>
  • Loading branch information
TheSadlig and GildasLebel authored Sep 26, 2024
1 parent 890481d commit 900a595
Show file tree
Hide file tree
Showing 36 changed files with 1,648 additions and 57 deletions.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ to the application/user. Just plug your application to your favorite message bro
* [Versioning](#versioning)
* [Extensions](#specification-extensions)
* [ErrorHandler](#errorhandler)
* [Validations](#validations)
* [Contributing and support](#contributing-and-support)

## Supported functionalities
Expand Down Expand Up @@ -779,6 +780,25 @@ func(ctx context.Context, topic string, msg *extensions.AcknowledgeableBrokerMes
}
```

### Validations

You can use [go-playground/validator](https://github.com/go-playground/validator) to validate the fields content against the contract.

The following tags are currently supported:

| Asyncapi | Validator tag | Comment |
|------------------|----------------|--------------------------------------------------------------|
| required | required | For a full support, the flag `--force-pointers` is necessary |
| minLength | min | |
| maxLength | max | |
| minimum | gte | |
| maximum | lte | |
| exclusiveMinimum | gt | |
| exclusiveMaximum | lt | |
| uniqueItems | unique | Only for arrays |
| enum | oneof | Only string enum are supported |


## Contributing and support

If you find any bug or lacking a feature, please raise an issue on the Github repository!
Expand Down
5 changes: 5 additions & 0 deletions cmd/asyncapi-codegen/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ type Flags struct {

// IgnoreStringFormat states whether the properties' format (date, date-time) should impact the type in types
IgnoreStringFormat bool

// ForcePointers can be used to force all struct fields to be generated as pointers
ForcePointers bool
}

// SetToCommand adds the flags to a cobra command.
Expand All @@ -63,6 +66,7 @@ func (f *Flags) SetToCommand(cmd *cobra.Command) {
"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")
cmd.Flags().BoolVar(&f.ForcePointers, "force-pointers", false, "Forces all struct fields to be generated as pointers")
}

// ToCodegenOptions processes command line flags structure to code generation tool options.
Expand All @@ -74,6 +78,7 @@ func (f Flags) ToCodegenOptions() (options.Options, error) {
ConvertKeys: f.ConvertKeys,
NamingScheme: f.NamingScheme,
IgnoreStringFormat: f.IgnoreStringFormat,
ForcePointers: f.ForcePointers,
}

if f.Generate != "" {
Expand Down
4 changes: 2 additions & 2 deletions examples/ping/v2/kafka/app/app.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions examples/ping/v2/kafka/user/user.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions examples/ping/v2/nats-jetstream/app/app.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions examples/ping/v2/nats-jetstream/user/user.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions examples/ping/v2/nats/app/app.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions examples/ping/v2/nats/user/user.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions pkg/codegen/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import (
asyncapiv2 "github.com/lerenn/asyncapi-codegen/pkg/asyncapi/v2"
asyncapiv3 "github.com/lerenn/asyncapi-codegen/pkg/asyncapi/v3"
generatorv2 "github.com/lerenn/asyncapi-codegen/pkg/codegen/generators/v2"
templatesv2 "github.com/lerenn/asyncapi-codegen/pkg/codegen/generators/v2/templates"
generatorv3 "github.com/lerenn/asyncapi-codegen/pkg/codegen/generators/v3"
templatesv3 "github.com/lerenn/asyncapi-codegen/pkg/codegen/generators/v3/templates"
"github.com/lerenn/asyncapi-codegen/pkg/codegen/options"
"github.com/lerenn/asyncapi-codegen/pkg/utils/template"
"golang.org/x/tools/imports"
Expand Down Expand Up @@ -91,6 +93,10 @@ func (cg CodeGen) Generate(opt options.Options) error {
if opt.IgnoreStringFormat {
template.DisableDateOrTimeGeneration()
}
if opt.ForcePointers {
templatesv2.ForcePointerOnFields()
templatesv3.ForcePointerOnFields()
}

// Process specification
if err := cg.specification.Process(); err != nil {
Expand Down
4 changes: 2 additions & 2 deletions pkg/codegen/generators/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ func GenerateJSONTags[T any](schema asyncapi.Validations[T], field string) strin

// GenerateValidateTags returns the "validate" tag for a given field in a struct, based on the asyncapi contract.
// This tag can then be used by go-playground/validator/v10 to validate the struct's content.
func GenerateValidateTags[T any](schema asyncapi.Validations[T]) string {
func GenerateValidateTags[T any](schema asyncapi.Validations[T], isPointer bool, schemaType string) string {
var directives []string
if schema.IsRequired {
if schema.IsRequired && (isPointer || schemaType == "array") {
directives = append(directives, "required")
}

Expand Down
12 changes: 12 additions & 0 deletions pkg/codegen/generators/v2/templates/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,25 @@ func OperationName(channel asyncapi.Channel) string {
return templateutil.Namify(name)
}

var isFieldPointer = func(parent asyncapi.Schema, field string, schema asyncapi.Schema) bool {
return !IsRequired(parent, field) && schema.Type != "array"
}

// ForcePointerOnFields is used to force the generation of all fields as pointers, except for arrays.
func ForcePointerOnFields() {
isFieldPointer = func(parent asyncapi.Schema, field string, schema asyncapi.Schema) bool {
return schema.Type != "array"
}
}

// HelpersFunctions returns the functions that can be used as helpers
// in a golang template.
func HelpersFunctions() template.FuncMap {
return template.FuncMap{
"getChildrenObjectSchemas": GetChildrenObjectSchemas,
"channelToMessage": ChannelToMessage,
"isRequired": IsRequired,
"isFieldPointer": isFieldPointer,
"generateChannelPath": GenerateChannelPath,
"referenceToStructAttributePath": ReferenceToStructAttributePath,
"operationName": OperationName,
Expand Down
2 changes: 1 addition & 1 deletion pkg/codegen/generators/v2/templates/schema_definition.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type {{ namify .Name }} struct {
{{else if and $value.ReferenceTo $value.ReferenceTo.Description}}
// Description: {{multiLineComment $value.ReferenceTo.Description}}
{{end -}}
{{namify $key}} {{if and (not (isRequired $ $key)) (ne $value.Type "array")}}*{{end}}{{template "schema-name" $value}} `{{generateJSONTags $value.Validations $key}}{{generateValidateTags $value.Validations}}`
{{namify $key}} {{if isFieldPointer $ $key $value }}*{{end}}{{template "schema-name" $value}} `{{generateJSONTags $value.Validations $key}}{{generateValidateTags $value.Validations (isFieldPointer $ $key $value) $value.Type }}`
{{end -}}

{{- if .AdditionalProperties}}
Expand Down
12 changes: 12 additions & 0 deletions pkg/codegen/generators/v3/templates/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,17 @@ func GenerateChannelAddr(ch *asyncapi.Channel) string {
return sprint[:len(sprint)-1] + ")"
}

var isFieldPointer = func(parent asyncapi.Schema, field string, schema asyncapi.Schema) bool {
return !IsRequired(parent, field) && schema.Type != "array"
}

// ForcePointerOnFields is used to force the generation of all fields as pointers, except for arrays.
func ForcePointerOnFields() {
isFieldPointer = func(parent asyncapi.Schema, field string, schema asyncapi.Schema) bool {
return schema.Type != "array"
}
}

// HelpersFunctions returns the functions that can be used as helpers
// in a golang template.
func HelpersFunctions() template.FuncMap {
Expand All @@ -136,6 +147,7 @@ func HelpersFunctions() template.FuncMap {
"opToMsgTypeName": OpToMsgTypeName,
"opToChannelTypeName": OpToChannelTypeName,
"isRequired": IsRequired,
"isFieldPointer": isFieldPointer,
"generateChannelAddr": GenerateChannelAddr,
"generateChannelAddrFromOp": GenerateChannelAddrFromOp,
"referenceToStructAttributePath": ReferenceToStructAttributePath,
Expand Down
2 changes: 1 addition & 1 deletion pkg/codegen/generators/v3/templates/schema_definition.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type {{ namify .Name }} struct {
{{else if and $value.ReferenceTo $value.ReferenceTo.Description}}
// Description: {{multiLineComment $value.ReferenceTo.Description}}
{{end -}}
{{namify $key}} {{if and (not (isRequired $ $key)) (ne $value.Type "array")}}*{{end}}{{template "schema-name" $value}} `{{generateJSONTags $value.Validations $key}}{{generateValidateTags $value.Validations}}`
{{namify $key}} {{if isFieldPointer $ $key $value }}*{{end}}{{template "schema-name" $value}} `{{generateJSONTags $value.Validations $key}}{{generateValidateTags $value.Validations (isFieldPointer $ $key $value) $value.Type }}`
{{end -}}

{{- if .AdditionalProperties}}
Expand Down
3 changes: 3 additions & 0 deletions pkg/codegen/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,7 @@ type Options struct {

// IgnoreStringFormat states whether the properties' format (date, date-time) should impact the type in types
IgnoreStringFormat bool

// ForcePointers can be used to force all struct fields to be generated as pointers
ForcePointers bool
}
2 changes: 1 addition & 1 deletion test/v2/issues/131/asyncapi.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 0 additions & 7 deletions test/v2/issues/131/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,6 @@ func (suite *Suite) TestFloat() {
assert.Error(suite.T(), validator.New().Struct(tooLarge))
}

func (suite *Suite) TestRequired() {
invalidAbsent := ValidTestSchema()
invalidAbsent.RequiredProp = ""

assert.Error(suite.T(), validator.New().Struct(invalidAbsent))
}

func (suite *Suite) TestArray() {
empty := ValidTestSchema()
empty.ArrayProp = []string{}
Expand Down
12 changes: 6 additions & 6 deletions test/v2/issues/185/asyncapi.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion test/v2/issues/245/asyncapi.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions test/v2/issues/259/asyncapi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
asyncapi: 2.6.0
info:
title: Sample App
version: 1.2.3

components:
messages:
Test:
payload:
type: object
required:
- reqField
- reqArray
properties:
reqField:
type: string
nonReqField:
type: string
reqArray:
type: array
items:
type: string
nonReqArray:
type: array
items:
type: string

Loading

0 comments on commit 900a595

Please sign in to comment.