Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gogenerate): add optional fields in log with omitempty annotation #619

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

iamrz1
Copy link

@iamrz1 iamrz1 commented Jan 22, 2025

What this PR does / why we need it

gobox cannot be re-stenciled due to manual change in autogenerated file where a field was made optional. this PR adds omitempty annotation to log tags, making it possible to mark fields optional.

Jira ID

DT-4572

Notes for your reviewers

@iamrz1 iamrz1 requested a review from a team as a code owner January 22, 2025 07:46
@iamrz1 iamrz1 changed the title feat(gogenrate): add optional fields in log with omitempty tag feat(gogenerate): add optional fields in log with omitempty annotation Jan 22, 2025
Copy link
Member

@malept malept left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps there should be a test for the changes you've made in processStruct?

Also, the new log annotation should be documented in tools/logger/generating.md.

Comment on lines 147 to 149
case strings.HasSuffix(field, tagOmitEmpty):
args["key"] = strings.TrimSuffix(field, tagOmitEmpty)
write(w, getSimpleOptionalFieldFormat(s.Field(kk).Type()), args)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: I think the parsing needs to happen after the if field, ok := [...] line (line 139) where you parse out the field and annotations, something like:

parts := strings.Split(",")
field = parts[0]
annotations := parts[1:] // I don't remember if this causes a panic

Comment on lines 195 to 198
return fmt.Sprintf(`
if s.{{.name}} != %s {
addField("{{.key}}", s.{{.name}})
}`, defaultValue)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: perhaps this string literal should be moved to the same area as the other templated strings (at the top of the file)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did that at first, but it didn't feel very intuitive because of the %s specifier. I will make this change, but I'm not really sure about it.

@iamrz1 iamrz1 requested a review from malept January 23, 2025 15:47
@@ -51,6 +51,41 @@ func (s *OrgInfo) MarshalLog(addField func(key string, value interface{})) {
}
```

### Annotations:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: don't put the trailing colon in the header, it doesn't make sense in the autogenerated table of contents.

Suggested change
### Annotations:
### Annotations

Comment on lines 66 to 68
type OrgInfo struct
Org string `log:"or.org.shortname,omitempty"`
Guid *Custom `log:"or.org.guid,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: examples should follow naming conventions. In particular:

  • the Info in OrgInfo is superfluous
  • Org is duplicative of the struct name
  • Guid should be in all caps since it's an acronym

Although I guess you need to fix it in the rest of the docs, too.

Suggested change
type OrgInfo struct
Org string `log:"or.org.shortname,omitempty"`
Guid *Custom `log:"or.org.guid,omitempty"`
type Org struct {
Shortname string `log:"or.org.shortname,omitempty"`
GUID *Custom `log:"or.org.guid,omitempty"`

}
```

Generated code:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: should probably note that the generated code here is post-formatted with gofmt

Comment on lines 55 to 56
These tags for fields also have limited support for annotations: `ie. log:"xyz,annotation"`.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: this line is superfluous.

Suggested change
These tags for fields also have limited support for annotations: `ie. log:"xyz,annotation"`.


Supported annotations:

- **omitempty**: makes the field optional.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: formatting

Suggested change
- **omitempty**: makes the field optional.
- `omitempty`: makes the field optional.

tools/logger/logger.go Show resolved Hide resolved
Comment on lines 61 to 62
`omitempty` annotation is available for simple built-in types or
custom types with underlying type being built-in or pointers of any type.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: avoid repeating yourself, grammar, use common terminology (Go calls them "basic types")

Suggested change
`omitempty` annotation is available for simple built-in types or
custom types with underlying type being built-in or pointers of any type.
This is available for basic types, types aliased to a basic type,
and pointers of any type. The annotation parser does **not** validate
that the annotation is only applied to supported types.

@@ -27,7 +27,7 @@ type OrgInfo struct

```

With the addition of the annotation (which matches the [standard
With the addition of the log tags `ie. log:"xyz"` for fields (xyz matches the [standard
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: wording

Suggested change
With the addition of the log tags `ie. log:"xyz"` for fields (xyz matches the [standard
With the addition of the `log` tags for fields (which matches the [standard

@@ -168,3 +187,31 @@ func usage() {
fmt.Fprintf(os.Stderr, "Flags:\n")
flag.PrintDefaults()
}

func getSimpleOptionalFieldFormat(p types.Type) string {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: is this format "simple" if it also covers pointers?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not anymore.

@iamrz1 iamrz1 requested a review from malept January 25, 2025 01:02
expected: true,
},
{
name: "uninitialized string slice",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: wrong test name?

Suggested change
name: "uninitialized string slice",
name: "uninitialized int slice",

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, copy paste.

@iamrz1 iamrz1 requested a review from malept January 25, 2025 04:20
@malept
Copy link
Member

malept commented Jan 27, 2025

@iamrz1 in the future, please avoid force pushing to PRs which have been reviewed. It makes it much more difficult to view commits which have been made since the last review. All repos in this org are set to "Squash and Merge" anyway.

@iamrz1
Copy link
Author

iamrz1 commented Jan 28, 2025

@malept Got it.

Copy link
Member

@malept malept left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems fine now, I just don't like the maintainability / readability of the ProcessStruct table test.

Comment on lines +87 to +203
return types.NewStruct(fields, tags), "BasicStruct"
},
expected: `
func (s *BasicStruct) MarshalLog(addField func(key string, value interface{})) {
if s == nil {
return
}

addField("name", s.Name)
addField("age", s.Age)
}
`,
},
{
name: "time field",
setup: func() (*types.Struct, string) {
pkg := types.NewPackage("time", "time")
timeType := types.NewNamed(types.NewTypeName(0, pkg, "Time", nil), &types.Struct{}, nil)

fields := []*types.Var{
types.NewField(0, nil, "CreatedAt", timeType, false),
}
tags := []string{`log:"created_at"`}
return types.NewStruct(fields, tags), "TimeStruct"
},
expected: `
func (s *TimeStruct) MarshalLog(addField func(key string, value interface{})) {
if s == nil {
return
}

addField("created_at", s.CreatedAt.UTC().Format(time.RFC3339Nano))
}
`,
},
{
name: "omitempty fields",
setup: func() (*types.Struct, string) {
fields := []*types.Var{
types.NewField(0, nil, "Name", types.Typ[types.String], false),
types.NewField(0, nil, "AgeP", types.NewPointer(types.Typ[types.Int]), false),
}
tags := []string{
`log:"name,omitempty"`,
`log:"ageP,omitempty"`,
}
return types.NewStruct(fields, tags), "OmitStruct"
},
expected: `
func (s *OmitStruct) MarshalLog(addField func(key string, value interface{})) {
if s == nil {
return
}

if s.Name != "" {
addField("name", s.Name)
}
if s.AgeP != nil {
addField("ageP", s.AgeP)
}
}
`,
},
{
name: "nested marshaler",
setup: func() (*types.Struct, string) {
nestedType := types.NewPointer(types.NewNamed(
types.NewTypeName(0, nil, "NestedStruct", nil),
&types.Struct{},
nil,
))
fields := []*types.Var{
types.NewField(0, nil, "Nested", nestedType, false),
}
tags := []string{`log:"."`}
return types.NewStruct(fields, tags), "ParentStruct"
},
expected: `
func (s *ParentStruct) MarshalLog(addField func(key string, value interface{})) {
if s == nil {
return
}

if s.Nested != nil {
s.Nested.MarshalLog(addField)
}
}
`,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, name := tt.setup()
buf := &bytes.Buffer{}
processStruct(buf, s, name)
assert.Equal(t, tt.expected, buf.String())
})
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: I don't agree that this should be a table test - the setup function makes it pretty complex to read.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants