## Go SDK Changes:

* `PlexApi.LibraryPlaylists.AddPlaylistItems()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Hubs.GetMetadataHubs()`: 
  *  `request.Request.OnlyTransient` **Changed**
  *  `response.MediaContainer.Hub.[].Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.LibraryCollections.MoveCollectionItem()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.LibraryCollections.DeleteCollectionItem()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.LibraryCollections.AddCollectionItems()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Content.GetSonicallySimilar()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Butler.StopTask()`:  `request.Request` **Changed** **Breaking** ⚠️
* `PlexApi.Butler.StartTask()`:  `request.Request` **Changed** **Breaking** ⚠️
* `PlexApi.Content.GetSonicPath()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.DownloadQueue.GetItemDecision()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Hubs.GetAllHubs()`: 
  *  `request.Request.OnlyTransient` **Changed**
  *  `response.MediaContainer.Hub.[].Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Hubs.GetContinueWatching()`:  `response.MediaContainer.Hub.[].Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Hubs.GetPromotedHubs()`:  `response.MediaContainer.Hub.[].Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Content.GetAllLeaves()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Hubs.GetPostplayHubs()`: 
  *  `request.Request.OnlyTransient` **Changed**
  *  `response.MediaContainer.Hub.[].Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Hubs.GetRelatedHubs()`: 
  *  `request.Request.OnlyTransient` **Changed**
  *  `response.MediaContainer.Hub.[].Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Hubs.GetSectionHubs()`: 
  *  `request.Request.OnlyTransient` **Changed**
  *  `response.MediaContainer.Hub.[].Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Content.ListContent()`: 
  *  `request.Request` **Changed**
  *  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Content.GetAlbums()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Search.SearchHubs()`:  `response.MediaContainer.Hub.[].Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Search.VoiceSearchHubs()`: 
  *  `request.Request.Type` **Changed** **Breaking** ⚠️
  *  `response.MediaContainer.Hub.[].Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Library.GetLibraryItems()`: 
  *  `request.Request.MediaQuery` **Changed**
  *  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Library.IngestTransientItem()`: 
  *  `request.Request` **Changed**
  *  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Library.GetLibraryMatches()`: 
  *  `request.Request` **Changed** **Breaking** ⚠️
  *  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Content.GetMetadataItem()`: 
  *  `request.Request` **Changed**
  *  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Library.GetSections()`:  `response.MediaContainer.Directory.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Library.AddSection()`: 
  *  `request.Request` **Changed**
  *  `response.MediaContainer.Directory.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Library.GetTags()`: 
  *  `request.Request.Type` **Changed** **Breaking** ⚠️
* `PlexApi.Content.GetCollectionItems()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Library.GetAllItemLeaves()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Status.ListSessions()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.PlayQueue.MovePlayQueueItem()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Library.GetExtras()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.PlayQueue.DeletePlayQueueItem()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.PlayQueue.Unshuffle()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Library.ListMatches()`: 
  *  `request.Request.Manual` **Changed**
  *  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Library.ListSonicallySimilar()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.PlayQueue.ResetPlayQueue()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Library.GetRelatedItems()`:  `response.MediaContainer.Hub.[].Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Library.ListSimilar()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.PlayQueue.ClearPlayQueue()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Library.GetItemTree()`:  `response.MediaContainer.MetadataItem.[]` **Changed** **Breaking** ⚠️
* `PlexApi.PlayQueue.AddToPlayQueue()`: 
  *  `request.Request.Next` **Changed**
  *  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.PlayQueue.GetPlayQueue()`: 
  *  `request.Request` **Changed**
  *  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.LibraryPlaylists.MovePlaylistItem()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Library.GetPerson()`:  `response.MediaContainer.Directory.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Library.ListPersonMedia()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.LibraryPlaylists.GetPlaylistGeneratorItems()`:  `response.MediaContainer.Metadata` **Changed** **Breaking** ⚠️
* `PlexApi.Library.GetLibraryDetails()`: 
  *  `request.Request.IncludeDetails` **Changed**
  *  `response.MediaContainer.Directory.[]` **Changed** **Breaking** ⚠️
* `PlexApi.LibraryPlaylists.ModifyPlaylistGenerator()`: 
  *  `request.Request.Item` **Changed**
  *  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Library.Autocomplete()`: 
  *  `request.Request.MediaQuery` **Changed**
  *  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Library.GetCollections()`: 
  *  `request.Request.MediaQuery` **Changed**
  *  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Library.GetCommon()`: 
  *  `request.Request.MediaQuery` **Changed**
  *  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.LibraryPlaylists.DeletePlaylistItem()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.LibraryPlaylists.ClearPlaylistItems()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.PlayQueue.Shuffle()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.LibraryPlaylists.CreatePlaylist()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Playlist.GetPlaylistItems()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Playlist.GetPlaylist()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Collections.CreateCollection()`: 
  *  `request.Request.Type` **Changed** **Breaking** ⚠️
  *  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.DvRs.TuneChannel()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.LiveTv.GetSessions()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.LiveTv.GetLiveTvSession()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Playlist.ListPlaylists()`:  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Subscriptions.GetAllSubscriptions()`: 
  *  `request.Request` **Changed**
  *  `response.MediaContainer.MediaSubscription.[].MediaGrabOperation.[].Metadata` **Changed** **Breaking** ⚠️
* `PlexApi.Subscriptions.CreateSubscription()`:  `response.MediaContainer.MediaSubscription.[].MediaGrabOperation.[].Metadata` **Changed** **Breaking** ⚠️
* `PlexApi.Subscriptions.GetScheduledRecordings()`:  `response.MediaContainer.MediaGrabOperation.[].Metadata` **Changed** **Breaking** ⚠️
* `PlexApi.Subscriptions.GetTemplate()`:  `response.MediaContainer.SubscriptionTemplate.[].MediaSubscription.[].MediaGrabOperation.[].Metadata` **Changed** **Breaking** ⚠️
* `PlexApi.Subscriptions.GetSubscription()`: 
  *  `request.Request` **Changed**
  *  `response.MediaContainer.MediaSubscription.[].MediaGrabOperation.[].Metadata` **Changed** **Breaking** ⚠️
* `PlexApi.Subscriptions.EditSubscriptionPreferences()`:  `response.MediaContainer.MediaSubscription.[].MediaGrabOperation.[].Metadata` **Changed** **Breaking** ⚠️
* `PlexApi.Subscriptions.ReorderSubscription()`:  `response.MediaContainer.MediaSubscription.[].MediaGrabOperation.[].Metadata` **Changed** **Breaking** ⚠️
* `PlexApi.Transcoder.MakeDecision()`: 
  *  `request.Request` **Changed**
  *  `response.MediaContainer.Metadata.[]` **Changed** **Breaking** ⚠️
* `PlexApi.Library.RefreshItemsMetadata()`: 
  *  `request.Request.MarkUpdated` **Changed**
* `PlexApi.Authentication.Post-Users-Sign-In-Data()`: **Added**
* `PlexApi.Transcoder.StartTranscodeSession()`:  `request.Request` **Changed**
* `PlexApi.Devices.ModifyDevice()`: 
  *  `request.Request.Enabled` **Changed**
* `PlexApi.Library.GetMediaPart()`: 
  *  `request.Request.Download` **Changed**
* `PlexApi.Library.DetectIntros()`: 
  *  `request.Request.Force` **Changed**
* `PlexApi.Library.RefreshSection()`: 
  *  `request.Request.Force` **Changed**
* `PlexApi.LibraryPlaylists.UploadPlaylist()`: 
  *  `request.Request.Force` **Changed**
* `PlexApi.Library.DeleteMediaItem()`: 
  *  `request.Request.Proxy` **Changed**
* `PlexApi.Authentication.GetTokenDetails()`: **Added**
* `PlexApi.Library.GetFirstCharacters()`: 
  *  `request.Request.MediaQuery` **Changed**
* `PlexApi.Library.UpdateItems()`: 
  *  `request.Request.Field.locked` **Changed**
* `PlexApi.Library.DeleteLibrarySection()`: 
  *  `request.Request.Async` **Changed**
* `PlexApi.Library.SetStreamSelection()`: 
  *  `request.Request.AllParts` **Changed**
* `PlexApi.PlayQueue.CreatePlayQueue()`:  `request.Request` **Changed**
* `PlexApi.Library.GetAugmentationStatus()`: 
  *  `request.Request.Wait` **Changed**
* `PlexApi.Library.DetectVoiceActivity()`:  `request.Request` **Changed**
* `PlexApi.Transcoder.TranscodeImage()`:  `request.Request` **Changed**
* `PlexApi.Transcoder.TranscodeSubtitles()`:  `request.Request` **Changed**
* `PlexApi.Library.AddSubtitles()`:  `request.Request` **Changed**
* `PlexApi.Library.GetStream()`: 
  *  `request.Request.AutoAdjustSubtitle` **Changed**
* `PlexApi.Library.StartBifGeneration()`: 
  *  `request.Request.Force` **Changed**
* `PlexApi.Library.DetectCredits()`:  `request.Request` **Changed**
* `PlexApi.UltraBlur.GetImage()`: 
  *  `request.Request.Noise` **Changed**
* `PlexApi.Library.GenerateThumbs()`: 
  *  `request.Request.Force` **Changed**
* `PlexApi.Updater.ApplyUpdates()`:  `request.Request` **Changed**
* `PlexApi.Updater.CheckUpdates()`: 
  *  `request.Request.Download` **Changed**
* `PlexApi.Library.DeleteMetadataItem()`: 
  *  `request.Request.Proxy` **Changed**
* `PlexApi.Library.OptimizeDatabase()`: 
  *  `request.Request.Async` **Changed**
* `PlexApi.Hubs.UpdateHubVisibility()`:  `request.Request` **Changed**
* `PlexApi.Hubs.CreateCustomHub()`:  `request.Request` **Changed**
* `PlexApi.Library.GetSectionImage()`: 
  *  `request.Request.MediaQuery` **Changed**
* `PlexApi.DownloadQueue.AddDownloadQueueItems()`:  `request.Request` **Changed**
* `PlexApi.Timeline.Report()`:  `request.Request` **Changed**
* `PlexApi.General.GetSourceConnectionInformation()`: 
  *  `request.Request.Refresh` **Changed**
* `PlexApi.Plex.Get-Server-Resources()`: **Added**
* `PlexApi.Users.Get-Users()`: **Added**
This commit is contained in:
speakeasybot
2025-12-01 00:15:28 +00:00
parent 3ca57c37e9
commit 3ae6d27c3c
273 changed files with 11257 additions and 2531 deletions

View File

@@ -14,13 +14,15 @@ import (
"github.com/LukeHagar/plexgo/types"
)
func populateForm(paramName string, explode bool, objType reflect.Type, objValue reflect.Value, delimiter string, defaultValue *string, getFieldName func(reflect.StructField) string) url.Values {
func populateForm(paramName string, explode bool, objType reflect.Type, objValue reflect.Value, delimiter string, defaultValue *string, allowEmptyValue map[string]struct{}, getFieldName func(reflect.StructField) string) url.Values {
formValues := url.Values{}
if isNil(objType, objValue) {
if defaultValue != nil {
formValues.Add(paramName, *defaultValue)
} else if _, ok := allowEmptyValue[paramName]; ok {
formValues.Add(paramName, "")
}
return formValues
@@ -103,12 +105,31 @@ func populateForm(paramName string, explode bool, objType reflect.Type, objValue
formValues.Add(paramName, strings.Join(items, delimiter))
}
case reflect.Slice, reflect.Array:
values := parseDelimitedArray(explode, objValue, delimiter)
for _, v := range values {
formValues.Add(paramName, v)
if objValue.Len() == 0 {
if _, ok := allowEmptyValue[paramName]; ok {
formValues.Add(paramName, "")
}
} else {
values := parseDelimitedArray(explode, objValue, delimiter)
for _, v := range values {
formValues.Add(paramName, v)
}
}
default:
formValues.Add(paramName, valToString(objValue.Interface()))
// For string types, use the value directly without conversion
if objType.Kind() == reflect.String {
stringValue := objValue.String()
formValues.Add(paramName, stringValue)
} else {
stringValue := valToString(objValue.Interface())
if stringValue == "" {
if _, ok := allowEmptyValue[paramName]; ok {
formValues.Add(paramName, "")
}
} else if stringValue != "" {
formValues.Add(paramName, stringValue)
}
}
}
return formValues

View File

@@ -658,17 +658,23 @@ func CalculateJSONSize(v interface{}) int {
// UnionCandidate represents a candidate type during union deserialization
type UnionCandidate struct {
FieldCount int
Size int
Type any // The union type enum value
Value any // The unmarshaled value
FieldCount int
InexactCount int // Count of fields with unknown/unrecognized enum values
Size int
Type any // The union type enum value
Value any // The unmarshaled value
}
// CountFields recursively counts the number of valid (non-nil, non-zero) fields set in a value.
// This is used as the primary discriminator for union types, with JSON size as a tiebreaker.
func CountFields(v interface{}) int {
// FieldCounts holds the result of counting fields in a value
type FieldCounts struct {
Total int // Total number of populated fields
Inexact int // Number of fields with unknown/unrecognized open enum values
}
// CountFieldsWithInexact recursively counts fields and tracks inexact matches (unknown open enum values).
func CountFieldsWithInexact(v interface{}) FieldCounts {
if v == nil {
return 0
return FieldCounts{}
}
typ := reflect.TypeOf(v)
@@ -677,7 +683,7 @@ func CountFields(v interface{}) int {
// Dereference pointers
for typ.Kind() == reflect.Ptr {
if val.IsNil() {
return 0
return FieldCounts{}
}
typ = typ.Elem()
val = val.Elem()
@@ -688,8 +694,9 @@ func CountFields(v interface{}) int {
// PickBestCandidate selects the best union type candidate using a multi-stage filtering approach:
// 1. If multiple candidates, filter by field count (keep only those with max field count)
// 2. If still multiple, filter by JSON size (keep only those with max size)
// 3. Return the first remaining candidate
// 2. If still multiple, filter by inexact count (keep only those with min inexact count)
// 3. If still multiple, filter by JSON size (keep only those with max size)
// 4. Return the first remaining candidate
func PickBestCandidate(candidates []UnionCandidate) *UnionCandidate {
if len(candidates) == 0 {
return nil
@@ -703,7 +710,9 @@ func PickBestCandidate(candidates []UnionCandidate) *UnionCandidate {
if len(candidates) > 1 {
maxFieldCount := -1
for i := range candidates {
candidates[i].FieldCount = CountFields(candidates[i].Value)
fieldCounts := CountFieldsWithInexact(candidates[i].Value)
candidates[i].FieldCount = fieldCounts.Total
candidates[i].InexactCount = fieldCounts.Inexact
if candidates[i].FieldCount > maxFieldCount {
maxFieldCount = candidates[i].FieldCount
}
@@ -723,6 +732,30 @@ func PickBestCandidate(candidates []UnionCandidate) *UnionCandidate {
return &candidates[0]
}
// Filter by inexact count if we still have multiple candidates
// Prefer candidates with fewer unknown/unrecognized enum values
if len(candidates) > 1 {
minInexactCount := int(^uint(0) >> 1) // max int
for _, c := range candidates {
if c.InexactCount < minInexactCount {
minInexactCount = c.InexactCount
}
}
// Keep only candidates with minimum inexact count
filtered := make([]UnionCandidate, 0, len(candidates))
for _, c := range candidates {
if c.InexactCount == minInexactCount {
filtered = append(filtered, c)
}
}
candidates = filtered
}
if len(candidates) == 1 {
return &candidates[0]
}
// Filter by JSON size if we still have multiple candidates
if len(candidates) > 1 {
maxSize := -1
@@ -747,8 +780,25 @@ func PickBestCandidate(candidates []UnionCandidate) *UnionCandidate {
return &candidates[0]
}
func countFieldsRecursive(typ reflect.Type, val reflect.Value) int {
count := 0
func countFieldsRecursive(typ reflect.Type, val reflect.Value) FieldCounts {
counts := FieldCounts{}
// Check if the value has an IsExact() method (for open enums)
// Try both the value and its pointer
if val.CanInterface() && val.CanAddr() {
ptrVal := val.Addr()
if method := ptrVal.MethodByName("IsExact"); method.IsValid() {
results := method.Call(nil)
if len(results) == 1 && results[0].Kind() == reflect.Bool {
isExact := results[0].Bool()
counts.Total = 1
if !isExact {
counts.Inexact = 1 // Unknown enum value
}
return counts
}
}
}
switch typ.Kind() {
case reflect.Struct:
@@ -756,18 +806,18 @@ func countFieldsRecursive(typ reflect.Type, val reflect.Value) int {
switch typ {
case reflect.TypeOf(time.Time{}):
if !val.Interface().(time.Time).IsZero() {
return 1
return FieldCounts{Total: 1}
}
return 0
return FieldCounts{}
case reflect.TypeOf(big.Int{}):
b := val.Interface().(big.Int)
if b.Sign() != 0 {
return 1
return FieldCounts{Total: 1}
}
return 0
return FieldCounts{}
case reflect.TypeOf(types.Date{}):
// Date is always counted if it exists
return 1
return FieldCounts{Total: 1}
}
// For regular structs, count non-zero fields
@@ -797,13 +847,15 @@ func countFieldsRecursive(typ reflect.Type, val reflect.Value) int {
}
if !isNil(field.Type, val.Field(i)) {
count += countFieldsRecursive(fieldTyp, fieldVal)
fieldCounts := countFieldsRecursive(fieldTyp, fieldVal)
counts.Total += fieldCounts.Total
counts.Inexact += fieldCounts.Inexact
}
}
case reflect.Slice, reflect.Array:
if val.IsNil() || val.Len() == 0 {
return 0
return FieldCounts{}
}
// Count each array/slice element
for i := 0; i < val.Len(); i++ {
@@ -820,40 +872,42 @@ func countFieldsRecursive(typ reflect.Type, val reflect.Value) int {
}
if !isNil(itemTyp, itemVal) {
count += countFieldsRecursive(itemTyp, itemVal)
itemCounts := countFieldsRecursive(itemTyp, itemVal)
counts.Total += itemCounts.Total
counts.Inexact += itemCounts.Inexact
}
}
case reflect.String:
if val.String() != "" {
count = 1
counts.Total = 1
}
case reflect.Bool:
// Bools always count as a field (even if false)
count = 1
counts.Total = 1
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if val.Int() != 0 {
count = 1
counts.Total = 1
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if val.Uint() != 0 {
count = 1
counts.Total = 1
}
case reflect.Float32, reflect.Float64:
if val.Float() != 0 {
count = 1
counts.Total = 1
}
default:
// For any other type, if it's not zero, count it as 1
if !val.IsZero() {
count = 1
counts.Total = 1
}
}
return count
return counts
}

View File

@@ -16,7 +16,7 @@ import (
"github.com/LukeHagar/plexgo/types"
)
func PopulateQueryParams(_ context.Context, req *http.Request, queryParams interface{}, globals interface{}) error {
func PopulateQueryParams(_ context.Context, req *http.Request, queryParams interface{}, globals interface{}, allowEmptyValue map[string]struct{}) error {
// Query parameters may already be present from overriding URL
if req.URL.RawQuery != "" {
return nil
@@ -24,13 +24,13 @@ func PopulateQueryParams(_ context.Context, req *http.Request, queryParams inter
values := url.Values{}
globalsAlreadyPopulated, err := populateQueryParams(queryParams, globals, values, []string{})
globalsAlreadyPopulated, err := populateQueryParams(queryParams, globals, values, []string{}, allowEmptyValue)
if err != nil {
return err
}
if globals != nil {
_, err = populateQueryParams(globals, nil, values, globalsAlreadyPopulated)
_, err = populateQueryParams(globals, nil, values, globalsAlreadyPopulated, allowEmptyValue)
if err != nil {
return err
}
@@ -41,7 +41,7 @@ func PopulateQueryParams(_ context.Context, req *http.Request, queryParams inter
return nil
}
func populateQueryParams(queryParams interface{}, globals interface{}, values url.Values, skipFields []string) ([]string, error) {
func populateQueryParams(queryParams interface{}, globals interface{}, values url.Values, skipFields []string, allowEmptyValue map[string]struct{}) ([]string, error) {
queryParamsVal := reflect.ValueOf(queryParams)
if queryParamsVal.Kind() == reflect.Pointer && queryParamsVal.IsNil() {
return nil, nil
@@ -101,14 +101,14 @@ func populateQueryParams(queryParams interface{}, globals interface{}, values ur
}
}
case "form":
vals := populateFormParams(qpTag, fieldType.Type, valType, ",", defaultValue)
vals := populateFormParams(qpTag, fieldType.Type, valType, ",", defaultValue, allowEmptyValue)
for k, v := range vals {
for _, vv := range v {
values.Add(k, vv)
}
}
case "pipeDelimited":
vals := populateFormParams(qpTag, fieldType.Type, valType, "|", defaultValue)
vals := populateFormParams(qpTag, fieldType.Type, valType, "|", defaultValue, allowEmptyValue)
for k, v := range vals {
for _, vv := range v {
values.Add(k, vv)
@@ -260,8 +260,8 @@ func populateDeepObjectParamsStruct(qsValues url.Values, priorScope string, stru
}
}
func populateFormParams(tag *paramTag, objType reflect.Type, objValue reflect.Value, delimiter string, defaultValue *string) url.Values {
return populateForm(tag.ParamName, tag.Explode, objType, objValue, delimiter, defaultValue, func(fieldType reflect.StructField) string {
func populateFormParams(tag *paramTag, objType reflect.Type, objValue reflect.Value, delimiter string, defaultValue *string, allowEmptyValue map[string]struct{}) url.Values {
return populateForm(tag.ParamName, tag.Explode, objType, objValue, delimiter, defaultValue, allowEmptyValue, func(fieldType reflect.StructField) string {
qpTag := parseQueryParamTag(fieldType)
if qpTag == nil {
return ""

View File

@@ -207,7 +207,7 @@ func encodeMultipartFormData(w io.Writer, data interface{}) (string, error) {
case reflect.Slice, reflect.Array:
values := parseDelimitedArray(true, valType, ",")
for _, v := range values {
if err := writer.WriteField(tag.Name+"[]", v); err != nil {
if err := writer.WriteField(tag.Name, v); err != nil {
writer.Close()
return "", err
}
@@ -325,7 +325,7 @@ func encodeFormData(fieldName string, w io.Writer, data interface{}) error {
switch tag.Style {
// TODO: support other styles
case "form":
values := populateForm(tag.Name, tag.Explode, fieldType, valType, ",", nil, func(sf reflect.StructField) string {
values := populateForm(tag.Name, tag.Explode, fieldType, valType, ",", nil, nil, func(sf reflect.StructField) string {
tag := parseFormTag(field)
if tag == nil {
return ""