-
-
Notifications
You must be signed in to change notification settings - Fork 48
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
Add struct tag support for field matching #164
base: main
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #164 +/- ##
==========================================
- Coverage 96.53% 95.13% -1.40%
==========================================
Files 46 44 -2
Lines 2250 2715 +465
==========================================
+ Hits 2172 2583 +411
- Misses 53 109 +56
+ Partials 25 23 -2 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the contribution, remarks in subcomments.
@@ -39,6 +39,7 @@ func (*Struct) Build(gen Generator, ctx *MethodContext, sourceID *xtype.JenID, s | |||
usedSourceID := false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change should have 100% patch coverage.
@@ -39,6 +39,7 @@ func (*Struct) Build(gen Generator, ctx *MethodContext, sourceID *xtype.JenID, s | |||
usedSourceID := false | |||
for i := 0; i < target.StructType.NumFields(); i++ { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be allowed to define matchTag multiple times. E.g. this should work:
// goverter:converter
type Converter interface {
// goverter:matchTag json
// goverter:matchTag yaml
Convert(source Input) Output
}
type Input struct {
A1 int `json:"a"`
A2 int `json:"b"`
}
type Output struct {
B1 int `yaml:"a"`
B2 int `yaml:"b"`
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or perhaps goverter:matchTag json yaml
applied in order of precedence?
I did think about having the matchTags be []string
vs string
, but there's a bit of extra complexity here. This could become a bit of a NxM comparison in cases like:
type Input struct {
A1 int `json:"a" yaml:"p"`
A2 int `json:"b"`
}
type Output struct {
B1 int `json:"p" yaml:"a"`
B2 int `yaml:"b"`
}
Does json yaml
or json\nyaml\n
imply precedence, in which case A1 would fail to match B1, or is the comparison exhaustive? (And would fail in the note you made about ambiguity causing an error.)
The multi-tag use case feels a bit more rare, so I was hesitant to add the complexity. But I'm happy to add it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree it's likely rare, but I don't think it adds that much complexity after #164 (comment) is supported.
There shouldn't be precedence, it should error like described here #164 (comment)
Basically all/most ambiguous cases are an error in goverter. E.g.
- duplicated enum values https://github.com/jmattheis/goverter/blob/main/scenario/enum_duplicated_different_values.yml
- multiple field name matches
@@ -39,6 +39,7 @@ func (*Struct) Build(gen Generator, ctx *MethodContext, sourceID *xtype.JenID, s | |||
usedSourceID := false | |||
for i := 0; i < target.StructType.NumFields(); i++ { | |||
targetField := target.StructType.Field(i) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
matchTag
shouldn't take precedence over name matching. If there is a clash, then goverter should error. E.g. this
// goverter:converter
type Converter interface {
// goverter:matchTag json
Convertre(source Input) Output
}
type Input struct {
A int `json:"B"`
B int `json:"A"`
}
type Output struct {
A int `json:"B"`
}
The error should be similar to this: https://github.com/jmattheis/goverter/blob/main/scenario/match_ignore_case_unresolved_ambiguity.yml
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This adds complexity, so I hesitated. Again, happy to add it.
@@ -39,6 +39,7 @@ func (*Struct) Build(gen Generator, ctx *MethodContext, sourceID *xtype.JenID, s | |||
usedSourceID := false | |||
for i := 0; i < target.StructType.NumFields(); i++ { | |||
targetField := target.StructType.Field(i) | |||
targetTag := target.StructType.Tag(i) | |||
delete(definedFields, targetField.Name()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
matchTag
should support matchIgnoreCase.
package example
// goverter:converter
type Converter interface {
// goverter:matchTag json
// goverter:matchIgnoreCase
Convertre(source Input) Output
}
type Input struct {
A1 int `json:"a"`
}
type Output struct {
B1 int `json:"A"`
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IME mixed json casing is rare, with matchIgnoreCase
being useful for matching "fooBar" to "FooBar" or "fooId" to "fooID". Because of the general rigor I've seen in tag naming I hesitated because of complexity, but yet again, happy to oblige. ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think there is a clear rule how to name these, and having different casing there doesn't seem to be that rare:
userId
1134 matches https://grep.app/search?q=json%3A%22userId®exp=true&case=trueuserID
349 matches https://grep.app/search?q=json%3A%22userID®exp=true&case=trueuserid
305 matches https://grep.app/search?q=json%3A%22userid®exp=true&case=trueUserId
75 matches https://grep.app/search?q=json%3A%22UserId®exp=true&case=true
`matchTag TAGNAME` can be defined as [CLI argument](./define-settings.md#cli) or | ||
[conversion comment](./define-settings.md#conversion). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is incorrect, it's a method comment that is inheritable. Use the text from https://goverter.jmattheis.de/reference/matchIgnoreCase
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this was something I was definitely not sure about.
|
||
// Protobuf tags are exciting, in that they /optionally/ have a json= section which takes | ||
// precedence over the name= section. | ||
if matchTag == "protobuf" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is not needed for the first version. The protobuf stuff should be already covered via json. So we don't need this custom parsing for protobuf.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem here is that the json:
tag that is generated from tools is snake-cased, whereas it's possible to configure UseProtoNames
to emit the camel-cased option in the protobuf: tag itself. Without this, I wouldn't be able to utilize tags. This is why I have the json:
fallback to cover both use cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm understood, I dislike specific implementations that aren't general in goverter and also the internal precedence from json/name protobuf key.
What do you think about a more generic implementation where the user can configure the delimiters?
type TagMatch struct {
Name string
Key string
FieldDelim string
ValueDelim string
}
fieldDelim=, is the default
matchTag json
is same as
matchTag json delimiter=,
matchTag protobuf valueDelim== key=name
matchTag protobuf valueDelim== key=json
matchTag gorm fieldDelm=; valueDelim=: key=column
https://gorm.io/docs/models.html
@@ -0,0 +1,32 @@ | |||
input: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file should match the casing of the other files in this directory (snake_case.yml)
@@ -0,0 +1,14 @@ | |||
package example |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The directory of this file should be the same format as the other directories. match-tag
`matchTag TAGNAME` can be defined as [CLI argument](./define-settings.md#cli) or | ||
[conversion comment](./define-settings.md#conversion). | ||
|
||
`matchTag` instructs goverter to use the given TAGNAME to exactly match fields between source and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add how the tag is parsed. (First value of ,
delimited)
golang.org/x/tools v0.17.0 | ||
github.com/dave/jennifer v1.7.1 | ||
github.com/stretchr/testify v1.9.0 | ||
golang.org/x/tools v0.24.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
go1.18 build is broken because of the dep update.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I thought I brought it up just before 1.18 incompatibility and those checks weren't running for me to notice. Will fix.
This PR adds support for using struct tags to match variable names.
Feedback welcome!
Following up on #132 this adds a new option that takes the name of the tag to use for matching. For example:
Tag matching is not performed if
matchTag
is not set, and matching falls back to standard code paths.Docs have been updated, and functionality seems good on my code base.