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

Add support for omitempty field tag to exclude empty values from queries and headers #107

Merged
merged 7 commits into from
May 20, 2024

Conversation

alecsammon
Copy link
Contributor

@alecsammon alecsammon commented May 4, 2024

Adds support for an omitempty tag.

Main use case is for query parameters where we may not want have empty keys

i.e. /pets?name_pointer=

By adding omitempty to the tag then this the key will not appear in the query string

i.e.

	type SearchQuery struct {
		NamePointer *string `in:"query=name_pointer;omitempty"`
	}

This will create /pets

This has been added to the formencoder so that it will also remove empty headers.

I have picked omitempty as I feel this will feel most natural for engineers as they are likely already used to this syntax with JSON.

@alecsammon
Copy link
Contributor Author

Build is failing due to rate limiting in CodeCov - will try a rebuild later.

@alecsammon alecsammon marked this pull request as ready for review May 4, 2024 11:31
Copy link
Owner

@ggicci ggicci left a comment

Choose a reason for hiding this comment

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

Thanks for the update!

README.md Outdated
Page int `in:"query=page;default=1"`
PerPage int `in:"query=per_page;default=20"`
IsMember bool `in:"query=is_member"`
Search *string `in:"query=search,omitempty"`
Copy link
Owner

Choose a reason for hiding this comment

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

The query directive is designed in the signautre as query(name1, name2, ...), here the omitempty will be read as the second name used in the query parameters. I'm thinking:

  1. if we should define a separate omitempty directive for this, i.e. in:"query=search;header=x-api-keyword;omitempty"?
  2. shall we just use patch.Field[T] as the field type, since it was designed for indicating absent values, i.e. Search patch.Field[string], when Search.Valid == false, it will be omitted from the final resource.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using patch.Field[T] for the field type would not be ideal for us as this would complicate using our library for consumers, if this can be handled purely by the stuct tags then this would be ideal.

I have updated it to use a new omitempty directive now. I do agree it is better.

I'm not sure on the implementation - as the new directive doesn't take any action by itself - but is used inside the existing directives - maybe this is ok, or maybe there is a better way?

Copy link
Owner

Choose a reason for hiding this comment

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

I totally agree. I hope patch.Field[T] should be used only in rare cases.


type FormEncoder struct {
Setter func(key string, value []string) // form value setter
}

func (e *FormEncoder) Execute(rtm *DirectiveRuntime) error {
tag := rtm.Resolver.Field.Tag.Get("in")
if rtm.Value.IsZero() && strings.Contains(tag, "omitempty") {
Copy link
Owner

Choose a reason for hiding this comment

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

This test is error prone. Either check

  1. rtm.Directive.Argv for omitempty defined in a directive, or
  2. rtm.Resolver.GetDirective for omitempty defined as a single directive

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you! Have gone with option 2.


type FormEncoder struct {
Setter func(key string, value []string) // form value setter
}

func (e *FormEncoder) Execute(rtm *DirectiveRuntime) error {
if rtm.Value.IsZero() {
for _, d := range rtm.Resolver.Directives {
if d.Name == "omitempty" {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

maybe we could use a constant here to link this back to the actual directive?

Copy link
Owner

Choose a reason for hiding this comment

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

I think it's okay. But we can use API rtm.Resolver.GetDirective("omitempty") here to simplify the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks! missed that.

// DirectiveOmitEmpty is used with the DirectiveQuery, DirectiveForm, and DirectiveHeader to indicate that the field
// should be omitted when the value is empty.
// It does not have any affect when used by itself
type DirectiveOmitEmpty struct{}
Copy link
Owner

Choose a reason for hiding this comment

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

In the json package, omitempty is a tag that affects both json marshalling and unmarshalling. Since we have modified the encoding behaviour of using omitempty here, I would suggest upgrading decoding as well to have the similar effects. Otherwise, it might bring up suprises for users who expect omitempty to work in the decoding function. Have you ever considered how it should behave in decoding?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that omitempty only affects Marshal with json, and not Unmarshal - as such I think it only makes sense to only modify encoding and not decoding.

Or maybe I'm missing something obvious!

https://go.dev/play/p/oKfcC5RIdQi

Copy link
Owner

Choose a reason for hiding this comment

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

Yeah, you're right. The omitempty tag only influences the marshalling process (i.e., converting a Go struct into JSON).

Copy link

codecov bot commented May 18, 2024

Codecov Report

Attention: Patch coverage is 77.77778% with 2 lines in your changes are missing coverage. Please review.

Project coverage is 90.70%. Comparing base (6a628af) to head (a7e1cb0).

Files Patch % Lines
core/omitempty.go 50.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #107      +/-   ##
==========================================
- Coverage   90.78%   90.70%   -0.08%     
==========================================
  Files          34       35       +1     
  Lines        1530     1539       +9     
==========================================
+ Hits         1389     1396       +7     
- Misses         97       99       +2     
  Partials       44       44              
Flag Coverage Δ
unittests 90.70% <77.77%> (-0.08%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@ggicci ggicci merged commit eed0621 into ggicci:main May 20, 2024
1 of 3 checks passed
@alecsammon alecsammon deleted the omitempty branch June 4, 2024 18:22
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