Skip to content

feat(binding): add support for encoding.UnmarshalText in uri/query binding #4203

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

takanuva15
Copy link
Contributor

@takanuva15 takanuva15 commented Mar 27, 2025

With this PR, users can now specify parser=encoding.TextUnmarshaler in their form/uri tag to enable automatic binding using the encoding.TextUnmarshaler interface from the Golang standard library. Since this new parser tag is 100% opt-in only, this is fully backwards-compatible with previous versions of gin.

Merging this PR will resolve a number of tickets regarding this functionality:

closes #4177


Sample code to run:

package main

import (
  "encoding"
  "strings"

  "github.com/gin-gonic/gin"
)

type Birthday string

func (b *Birthday) UnmarshalText(text []byte) error {
  *b = Birthday(strings.Replace(string(text), "-", "/", -1))
  return nil
}

var _ encoding.TextUnmarshaler = (*Birthday)(nil) //assert Birthday implements encoding.TextUnmarshaler

func main() {
  route := gin.Default()
  var request struct {
    Birthday         Birthday   `form:"birthday,parser=encoding.TextUnmarshaler"`
    Birthdays        []Birthday `form:"birthdays,parser=encoding.TextUnmarshaler" collection_format:"csv"`
    BirthdaysDefault []Birthday `form:"birthdaysDef,default=2020-09-01;2020-09-02,parser=encoding.TextUnmarshaler" collection_format:"csv"`
  }
  route.GET("/test", func(ctx *gin.Context) {
    _ = ctx.BindQuery(&request)
    ctx.JSON(200, request)
  })
  _ = route.Run(":8088")
}

Test it with:

curl 'localhost:8088/test?birthday=2000-01-01&birthdays=2000-01-01,2000-01-02'

Result

{"Birthday":"2000/01/01","Birthdays":["2000/01/01","2000/01/02"],"BirthdaysDefault":["2020/09/01","2020/09/02"]

Copy link

codecov bot commented Mar 27, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 98.92%. Comparing base (3dc1cd6) to head (fa25271).
Report is 127 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4203      +/-   ##
==========================================
- Coverage   99.21%   98.92%   -0.29%     
==========================================
  Files          42       44       +2     
  Lines        3182     3453     +271     
==========================================
+ Hits         3157     3416     +259     
- Misses         17       26       +9     
- Partials        8       11       +3     
Flag Coverage Δ
?
--ldflags="-checklinkname=0" -tags sonic 98.86% <100.00%> (?)
-tags go_json 98.86% <100.00%> (?)
-tags nomsgpack 98.91% <100.00%> (?)
go-1.18 ?
go-1.19 ?
go-1.20 ?
go-1.21 ?
go-1.23 98.92% <100.00%> (?)
go-1.24 98.92% <100.00%> (?)
macos-latest 98.92% <100.00%> (-0.29%) ⬇️
ubuntu-latest 98.92% <100.00%> (-0.29%) ⬇️

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.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@takanuva15 takanuva15 marked this pull request as ready for review March 27, 2025 18:30
@takanuva15
Copy link
Contributor Author

@appleboy ready for review. I verified locally that all my new changes are covered with the new tests I added. The linting failure is unrelated to my changes since I did not add any sprintf calls to my code

@appleboy appleboy requested a review from Copilot May 20, 2025 15:13
@appleboy appleboy added this to the v1.11 milestone May 20, 2025
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds support for encoding.TextUnmarshaler-based binding via a new parser=encoding.TextUnmarshaler tag option.

  • Introduce a parser option in form_mapping.go and implement trySetUsingParser
  • Update setByForm and related functions to invoke UnmarshalText when parser is set
  • Enhance documentation (docs/doc.md) with usage examples and notes

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
docs/doc.md Add new “Bind custom unmarshaler” section and notes for parser tag
binding/form_mapping.go Parse parser tag, implement trySetUsingParser, wire into setter
Comments suppressed due to low confidence (2)

binding/form_mapping.go:199

  • There are no unit tests covering the new parser=encoding.TextUnmarshaler behavior. Consider adding tests for simple values, slices, and default-value scenarios to prevent regressions.
func trySetUsingParser(val string, value reflect.Value, parser string) (isSet bool, err error) {

docs/doc.md:1054

  • The JSON example is missing a closing } at the end; add the trailing brace to make the output valid JSON.
{"Birthday":"2000/01/01","Birthdays":["2000/01/01","2000/01/02"],"BirthdaysDefault":["2020/09/01","2020/09/02"]

Comment on lines +261 to 264
if ok, err = trySetUsingParser(vs[0], value, opt.parser); ok {
return ok, err
} else if ok, err = trySetCustom(vs[0], value); ok {
return ok, err
Copy link
Preview

Copilot AI May 20, 2025

Choose a reason for hiding this comment

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

[nitpick] The pattern of invoking trySetUsingParser followed by trySetCustom is repeated in multiple branches. Extracting this into a small helper would reduce duplication and improve readability.

Suggested change
if ok, err = trySetUsingParser(vs[0], value, opt.parser); ok {
return ok, err
} else if ok, err = trySetCustom(vs[0], value); ok {
return ok, err
if ok, err = trySetValue(vs[0], value, opt); ok {
return ok, err

Copilot uses AI. Check for mistakes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's only repeated once. Extracting it to a function will make this code even more difficult to understand than it already is

@appleboy
Copy link
Member

Please rebase the master branch. @takanuva15

@takanuva15
Copy link
Contributor Author

@appleboy done, just rebased it now

@takanuva15
Copy link
Contributor Author

I'm not sure why the coverage shows as dropped, but I just ran a full coverage report via

go test -cover -coverprofile=c.out ./... -coverpkg=./... \
      && go tool cover -html=c.out -o coverage.html

and it shows 100% coverage on form_mapping.go, which is the only file I modified.

image

@appleboy
Copy link
Member

@takanuva15 Please help to resolve the conflicts.

@takanuva15 takanuva15 force-pushed the master branch 5 times, most recently from d37dc58 to ae06b4d Compare May 27, 2025 13:42
@takanuva15
Copy link
Contributor Author

@takanuva15 Please help to resolve the conflicts.

done

@appleboy appleboy requested a review from Copilot May 28, 2025 01:37
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Adds support for the parser=encoding.TextUnmarshaler tag in form/URI binding so users can opt in to encoding.TextUnmarshaler-based decoding.

  • Updated docs with examples and behavior notes for the new parser tag.
  • Extended binding/form_mapping.go to parse a parser option, added trySetUsingParser, and wired it into slice, array, and scalar binding.
  • Broadened the error message for unsupported collection_format to list all valid formats.

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
docs/doc.md Added UnmarshalText examples, notes on parser behavior
binding/form_mapping.go Introduced parser field in setOptions, trySetUsingParser, and updated binding logic
Comments suppressed due to low confidence (2)

binding/form_mapping.go:199

  • No unit tests cover the new parser=encoding.TextUnmarshaler path; please add tests for both successful and failure scenarios when the target type does and does not implement TextUnmarshaler.
func trySetUsingParser(val string, value reflect.Value, parser string) (isSet bool, err error) {

docs/doc.md:1106

  • The JSON example is missing a closing }. Please add the final brace to make the example valid JSON.
{"Birthday":"2000/01/01","Birthdays":["2000/01/01","2000/01/02"],"BirthdaysDefault":["2020/09/01","2020/09/02"]

Comment on lines +261 to 264
if ok, err = trySetUsingParser(vs[0], value, opt.parser); ok {
return ok, err
} else if ok, err = trySetCustom(vs[0], value); ok {
return ok, err
Copy link
Preview

Copilot AI May 28, 2025

Choose a reason for hiding this comment

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

[nitpick] The pattern of calling trySetUsingParser and then trySetCustom is repeated in multiple branches. Consider unifying this sequence into a helper to reduce duplication and improve clarity.

Suggested change
if ok, err = trySetUsingParser(vs[0], value, opt.parser); ok {
return ok, err
} else if ok, err = trySetCustom(vs[0], value); ok {
return ok, err
if ok, err = trySetValue(vs[0], value, opt.parser); ok {
return ok, err

Copilot uses AI. Check for mistakes.

Copy link
Contributor Author

@takanuva15 takanuva15 May 28, 2025

Choose a reason for hiding this comment

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

It's only repeated once. Extracting it to a function will make this code even more difficult to understand than it already is.

Other comments;

  • test coverage: form_mapping.go is at 100% test coverage, which is the only file I edited
  • doc json mistake: fixed it and repushed

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

Successfully merging this pull request may close these issues.

Add support for UnmarshalText from Golang encoding.TextUnmarshaler for URI and Query param binding
2 participants