mirror of
https://github.com/LukeHagar/plexgo.git
synced 2025-12-06 04:20:46 +00:00
## 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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
Reference in New Issue
Block a user