@@ -27,6 +27,7 @@ import (
2727 "strconv"
2828 "strings"
2929 "time"
30+ "unicode"
3031)
3132
3233var timeType = reflect .TypeOf (time.Time {})
@@ -39,6 +40,36 @@ type Encoder interface {
3940 EncodeValues (key string , v * url.Values ) error
4041}
4142
43+ // Case defines the struct field name casing format used when encoding query parameters.
44+ type Case int
45+
46+ const (
47+ // CaseDefault uses the struct field's name as defined in the struct (usually PascalCase).
48+ CaseDefault Case = iota
49+ // CaseCamel converts struct field names to camelCase (e.g. "firstName", "userId").
50+ CaseCamel
51+ // CaseSnake converts struct field names to snake_case (e.g. "first_name", "user_id").
52+ CaseSnake
53+ // CasePascal converts struct field names to PascalCase (e.g. "FirstName", "UserId").
54+ CasePascal
55+ // CaseKebab converts struct field names to kebab-case (e.g. "first-name", "user-id").
56+ CaseKebab
57+ )
58+
59+ // Uppercase constants for compatibility with Issue #86 specification.
60+ const (
61+ CASE_DEFAULT = CaseDefault
62+ CASE_CAMEL = CaseCamel
63+ CASE_SNAKE = CaseSnake
64+ CASE_PASCAL = CasePascal
65+ CASE_KEBAB = CaseKebab
66+ )
67+
68+ // Options specifies optional configuration parameters for encoding query values.
69+ type Options struct {
70+ EncodingCase Case
71+ }
72+
4273// Values returns the url.Values encoding of v.
4374//
4475// Values expects to be passed a struct, and traverses it recursively using the
@@ -122,7 +153,16 @@ type Encoder interface {
122153//
123154// Multiple fields that encode to the same URL parameter name will be included
124155// as multiple URL values of the same name.
125- func Values (v interface {}) (url.Values , error ) {
156+ func Values (v interface {}, opts ... Options ) (url.Values , error ) {
157+ var opt Options
158+ if len (opts ) > 0 {
159+ opt = opts [0 ]
160+ }
161+ return ValuesWithOptions (v , opt )
162+ }
163+
164+ // ValuesWithOptions returns the url.Values encoding of v with the specified Options.
165+ func ValuesWithOptions (v interface {}, opts Options ) (url.Values , error ) {
126166 values := make (url.Values )
127167
128168 if v == nil {
@@ -141,14 +181,14 @@ func Values(v interface{}) (url.Values, error) {
141181 return nil , fmt .Errorf ("query: Values() expects struct input. Got %v" , val .Kind ())
142182 }
143183
144- err := reflectValue (values , val , "" )
184+ err := reflectValue (values , val , "" , opts )
145185 return values , err
146186}
147187
148188// reflectValue populates the values parameter from the struct fields in val.
149189// Embedded structs are followed recursively (using the rules defined in the
150190// Values function documentation) breadth-first.
151- func reflectValue (values url.Values , val reflect.Value , scope string ) error {
191+ func reflectValue (values url.Values , val reflect.Value , scope string , options Options ) error {
152192 var embedded []reflect.Value
153193
154194 typ := val .Type ()
@@ -176,6 +216,17 @@ func reflectValue(values url.Values, val reflect.Value, scope string) error {
176216 }
177217
178218 name = sf .Name
219+ if opts .Contains ("snake" ) {
220+ name = applyCase (name , CaseSnake )
221+ } else if opts .Contains ("camel" ) {
222+ name = applyCase (name , CaseCamel )
223+ } else if opts .Contains ("kebab" ) {
224+ name = applyCase (name , CaseKebab )
225+ } else if opts .Contains ("pascal" ) {
226+ name = applyCase (name , CasePascal )
227+ } else if options .EncodingCase != CaseDefault {
228+ name = applyCase (name , options .EncodingCase )
229+ }
179230 }
180231
181232 if scope != "" {
@@ -262,7 +313,7 @@ func reflectValue(values url.Values, val reflect.Value, scope string) error {
262313 }
263314
264315 if sv .Kind () == reflect .Struct {
265- if err := reflectValue (values , sv , name ); err != nil {
316+ if err := reflectValue (values , sv , name , options ); err != nil {
266317 return err
267318 }
268319 continue
@@ -272,7 +323,7 @@ func reflectValue(values url.Values, val reflect.Value, scope string) error {
272323 }
273324
274325 for _ , f := range embedded {
275- if err := reflectValue (values , f , scope ); err != nil {
326+ if err := reflectValue (values , f , scope , options ); err != nil {
276327 return err
277328 }
278329 }
@@ -365,3 +416,96 @@ func (o tagOptions) Contains(option string) bool {
365416 }
366417 return false
367418}
419+
420+ // applyCase converts name to the requested Case format.
421+ func applyCase (name string , c Case ) string {
422+ if c == CaseDefault || name == "" {
423+ return name
424+ }
425+ return formatWords (splitWords (name ), c )
426+ }
427+
428+ func splitWords (s string ) []string {
429+ var words []string
430+ var word []rune
431+ runes := []rune (s )
432+
433+ for i := 0 ; i < len (runes ); i ++ {
434+ r := runes [i ]
435+ if r == '_' || r == '-' || r == ' ' {
436+ if len (word ) > 0 {
437+ words = append (words , string (word ))
438+ word = word [:0 ]
439+ }
440+ continue
441+ }
442+
443+ if i > 0 {
444+ prev := runes [i - 1 ]
445+ if unicode .IsUpper (r ) && (unicode .IsLower (prev ) || unicode .IsDigit (prev )) {
446+ if len (word ) > 0 {
447+ words = append (words , string (word ))
448+ word = word [:0 ]
449+ }
450+ } else if unicode .IsUpper (prev ) && unicode .IsUpper (r ) && i + 1 < len (runes ) && unicode .IsLower (runes [i + 1 ]) {
451+ if len (word ) > 0 {
452+ words = append (words , string (word ))
453+ word = word [:0 ]
454+ }
455+ }
456+ }
457+
458+ word = append (word , r )
459+ }
460+
461+ if len (word ) > 0 {
462+ words = append (words , string (word ))
463+ }
464+ return words
465+ }
466+
467+ func formatWords (words []string , c Case ) string {
468+ if len (words ) == 0 {
469+ return ""
470+ }
471+ switch c {
472+ case CaseSnake :
473+ parts := make ([]string , len (words ))
474+ for i , w := range words {
475+ parts [i ] = strings .ToLower (w )
476+ }
477+ return strings .Join (parts , "_" )
478+ case CaseKebab :
479+ parts := make ([]string , len (words ))
480+ for i , w := range words {
481+ parts [i ] = strings .ToLower (w )
482+ }
483+ return strings .Join (parts , "-" )
484+ case CaseCamel :
485+ var b strings.Builder
486+ for i , w := range words {
487+ runes := []rune (w )
488+ if i == 0 {
489+ b .WriteString (strings .ToLower (w ))
490+ } else {
491+ b .WriteString (string (unicode .ToUpper (runes [0 ])))
492+ if len (runes ) > 1 {
493+ b .WriteString (strings .ToLower (string (runes [1 :])))
494+ }
495+ }
496+ }
497+ return b .String ()
498+ case CasePascal :
499+ var b strings.Builder
500+ for _ , w := range words {
501+ runes := []rune (w )
502+ b .WriteString (string (unicode .ToUpper (runes [0 ])))
503+ if len (runes ) > 1 {
504+ b .WriteString (strings .ToLower (string (runes [1 :])))
505+ }
506+ }
507+ return b .String ()
508+ default :
509+ return strings .Join (words , "" )
510+ }
511+ }
0 commit comments