From 2d3df9b5569cfef3a8c00095857c59d004314420 Mon Sep 17 00:00:00 2001 From: bensynapse <118375461+bensynapse@users.noreply.github.com> Date: Fri, 9 Oct 2026 02:44:04 +0300 Subject: [PATCH] query: skip unexported embedded scalar fields --- query/encode.go | 10 ++++- query/encode_test.go | 90 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/query/encode.go b/query/encode.go index ffb2097..c39a64b 100644 --- a/query/encode.go +++ b/query/encode.go @@ -154,8 +154,14 @@ func reflectValue(values url.Values, val reflect.Value, scope string) error { typ := val.Type() for i := 0; i < typ.NumField(); i++ { sf := typ.Field(i) - if sf.PkgPath != "" && !sf.Anonymous { // unexported - continue + if sf.PkgPath != "" { // unexported + embeddedType := sf.Type + if embeddedType.Kind() == reflect.Ptr { + embeddedType = embeddedType.Elem() + } + if !sf.Anonymous || embeddedType.Kind() != reflect.Struct { + continue + } } sv := val.Field(i) diff --git a/query/encode_test.go b/query/encode_test.go index 241e4f1..96d599f 100644 --- a/query/encode_test.go +++ b/query/encode_test.go @@ -424,6 +424,96 @@ func TestValues_EmbeddedStructs(t *testing.T) { } } +func TestValues_UnexportedEmbeddedFields(t *testing.T) { + type privateString string + type PublicString string + type privateStruct struct { + Limit int `url:"limit"` + } + value := privateString("private") + tests := []struct { + name string + input interface{} + want url.Values + }{ + { + "scalar", + struct { + privateString + Page int `url:"page"` + }{value, 2}, + url.Values{"page": {"2"}}, + }, + { + "scalar pointer", + struct { + *privateString + Page int `url:"page"` + }{&value, 2}, + url.Values{"page": {"2"}}, + }, + { + "nil scalar pointer", + struct { + *privateString + Page int `url:"page"` + }{nil, 2}, + url.Values{"page": {"2"}}, + }, + { + "tagged scalar", + struct { + privateString `url:"private"` + Page int `url:"page"` + }{value, 2}, + url.Values{"page": {"2"}}, + }, + { + "custom encoder", + struct { + customEncodedStrings + Page int `url:"page"` + }{customEncodedStrings{"private"}, 2}, + url.Values{"page": {"2"}}, + }, + { + "exported scalar", + struct { + PublicString + Page int `url:"page"` + }{"public", 2}, + url.Values{"PublicString": {"public"}, "page": {"2"}}, + }, + { + "promoted struct field", + struct { + privateStruct + Page int `url:"page"` + }{privateStruct{Limit: 3}, 2}, + url.Values{"limit": {"3"}, "page": {"2"}}, + }, + { + "promoted pointer field", + struct { + *privateStruct + Page int `url:"page"` + }{&privateStruct{Limit: 3}, 2}, + url.Values{"limit": {"3"}, "page": {"2"}}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer func() { + if err := recover(); err != nil { + t.Fatalf("Values panicked: %v", err) + } + }() + testValue(t, tt.input, tt.want) + }) + } +} + func TestValues_InvalidInput(t *testing.T) { _, err := Values("") if err == nil {