Skip to content

Commit 22e0c32

Browse files
vragovRVragov Roman
andauthored
feat: add support for controlling "omitempty" via x-omitempty extension (#276)
Co-authored-by: Vragov Roman <vragov@retailcrm.ru>
1 parent 4692a76 commit 22e0c32

File tree

13 files changed

+459
-4
lines changed

13 files changed

+459
-4
lines changed

README.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,32 @@ These extension properties apply to "Schema Objects" in AsyncAPI spec.
740740
}
741741
```
742742

743+
* `x-omitempty`: Controls the addition of the `omitempty` tag in JSON tags of generated Go structures.
744+
When set to `false`, the `omitempty` tag won't be added even for optional fields.
745+
746+
For example,
747+
748+
```yaml
749+
schemas:
750+
User:
751+
type: object
752+
properties:
753+
id:
754+
type: string
755+
name:
756+
type: string
757+
x-omitempty: false
758+
```
759+
760+
will be generated as
761+
762+
```go
763+
type User struct {
764+
Id *string `json:"id,omitempty"`
765+
Name *string `json:"name"`
766+
}
767+
```
768+
743769
### ErrorHandler
744770

745771
You can use an error handler that will be executed when processing for messages
@@ -773,7 +799,7 @@ ctrl, _ := NewAppController(/* Broker of your choice */, WithErrorHandler(errorh
773799
func(ctx context.Context, topic string, msg *extensions.AcknowledgeableBrokerMessage, err error) {
774800
// check error or move message to some other queue/topic
775801
handleTheErrorSomehow()
776-
802+
777803
// Ack or Nak the message
778804
msg.Ack()
779805
msg.Nak()

pkg/asyncapi/schema.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,13 @@ type Validations[T any] struct {
2525
OneOf []*T `json:"oneOf"`
2626

2727
// --- Non JSON Schema/AsyncAPI fields -------------------------------------
28-
IsRequired bool `json:"-"`
28+
IsRequired bool `json:"-"`
29+
ShouldOmitEmpty *bool `json:"-"`
2930
}
3031

3132
// Merge merges the newV into the current Validations.
3233
//
33-
//nolint:cyclop // This function is a merge function and it is expected to have a high cyclomatic complexity.
34+
//nolint:cyclop,funlen // This function is a merge function and it is expected to have a high cyclomatic complexity.
3435
func (v *Validations[T]) Merge(newV Validations[T]) {
3536
if len(newV.Required) > 0 {
3637
v.Required = newV.Required
@@ -92,4 +93,7 @@ func (v *Validations[T]) Merge(newV Validations[T]) {
9293
if newV.IsRequired {
9394
v.IsRequired = newV.IsRequired
9495
}
96+
if newV.ShouldOmitEmpty != nil {
97+
v.ShouldOmitEmpty = newV.ShouldOmitEmpty
98+
}
9599
}

pkg/asyncapi/v2/extensions.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ type Extensions struct {
1313

1414
// Setting custom import statements for ExtGoType
1515
ExtGoTypeImport *GoTypeImportExtension `json:"x-go-type-import"`
16+
17+
// Controls whether to include omitempty in JSON tags
18+
// If false, omitempty will be removed from JSON tags even if the field can be null
19+
ExtOmitEmpty *bool `json:"x-omitempty"`
1620
}
1721

1822
// GoTypeImportExtension specifies the required import statement

pkg/asyncapi/v2/schema.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ func (s *Schema) generateMetadata(name string, isRequired bool) error {
101101
s.Type = "string"
102102
}
103103
}
104+
105+
s.ShouldOmitEmpty = s.ExtOmitEmpty
106+
104107
return nil
105108
}
106109

pkg/asyncapi/v3/extensions.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ type Extensions struct {
1313

1414
// Setting custom import statements for ExtGoType
1515
ExtGoTypeImport *GoTypeImportExtension `json:"x-go-type-import"`
16+
17+
// Controls whether to include omitempty in JSON tags
18+
// If false, omitempty will be removed from JSON tags even if the field can be null
19+
ExtOmitEmpty *bool `json:"x-omitempty"`
1620
}
1721

1822
// GoTypeImportExtension specifies the required import statement

pkg/asyncapi/v3/schema.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ func (s *Schema) generateMetadata(parentName, name string, number *int, isRequir
161161
// Set IsRequired
162162
s.IsRequired = isRequired
163163

164+
s.ShouldOmitEmpty = s.ExtOmitEmpty
165+
164166
return nil
165167
}
166168

pkg/codegen/generators/helpers.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,21 @@ func GenerateJSONTags[T any](schema asyncapi.Validations[T], field string) strin
2121
template.ConvertKey(field),
2222
}
2323

24-
if !schema.IsRequired {
24+
if shouldAddOmitEmpty(schema) {
2525
directives = append(directives, "omitempty")
2626
}
2727

2828
return fmt.Sprintf("json:\"%s\"", strings.Join(directives, ","))
2929
}
3030

31+
func shouldAddOmitEmpty[T any](schema asyncapi.Validations[T]) bool {
32+
if schema.IsRequired {
33+
return schema.ShouldOmitEmpty != nil && *schema.ShouldOmitEmpty
34+
}
35+
36+
return schema.ShouldOmitEmpty == nil || *schema.ShouldOmitEmpty
37+
}
38+
3139
// GenerateValidateTags returns the "validate" tag for a given field in a struct, based on the asyncapi contract.
3240
// This tag can then be used by go-playground/validator/v10 to validate the struct's content.
3341
func GenerateValidateTags[T any](schema asyncapi.Validations[T], isPointer bool, schemaType string) string {

test/v2/issues/275/asyncapi.gen.go

Lines changed: 137 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/v2/issues/275/asyncapi.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
asyncapi: 2.6.0
2+
info:
3+
title: OmitEmpty Test
4+
version: 1.0.0
5+
6+
channels:
7+
v2.omitempty.test:
8+
subscribe:
9+
message:
10+
payload:
11+
$ref: '#/components/schemas/Test'
12+
13+
components:
14+
schemas:
15+
Test:
16+
type: object
17+
required:
18+
- requiredWithOmitEmpty
19+
properties:
20+
withOmitEmpty:
21+
type: string
22+
description: "This field should have omitempty in the JSON tag"
23+
withoutOmitEmpty:
24+
type: string
25+
description: "This field should NOT have omitempty in the JSON tag"
26+
x-omitempty: false
27+
requiredWithOmitEmpty:
28+
type: string
29+
description: "This field should have omitempty in the JSON tag"
30+
x-omitempty: true

test/v2/issues/275/suite_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//go:generate go run ../../../../cmd/asyncapi-codegen -p issue275 -g types -i ./asyncapi.yaml -o ./asyncapi.gen.go
2+
3+
package issue275
4+
5+
import (
6+
"encoding/json"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/suite"
11+
)
12+
13+
func TestSuite(t *testing.T) {
14+
suite.Run(t, NewSuite())
15+
}
16+
17+
type Suite struct {
18+
suite.Suite
19+
}
20+
21+
func NewSuite() *Suite {
22+
return &Suite{}
23+
}
24+
25+
func (suite *Suite) TestMarshal_OmitEmpty() {
26+
var res TestSchema
27+
data, err := json.Marshal(res)
28+
assert.NoError(suite.T(), err)
29+
assert.JSONEq(suite.T(), "{\"withoutOmitEmpty\":null}", string(data))
30+
}

0 commit comments

Comments
 (0)