mirror of
https://github.com/LukeHagar/plexgo.git
synced 2025-12-06 04:20:46 +00:00
* `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**
459 lines
12 KiB
Go
459 lines
12 KiB
Go
// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.
|
|
|
|
package utils
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"mime"
|
|
"mime/multipart"
|
|
"net/textproto"
|
|
"net/url"
|
|
"path/filepath"
|
|
"reflect"
|
|
"regexp"
|
|
|
|
"github.com/LukeHagar/plexgo/optionalnullable"
|
|
)
|
|
|
|
const (
|
|
requestTagKey = "request"
|
|
multipartFormTagKey = "multipartForm"
|
|
formTagKey = "form"
|
|
)
|
|
|
|
var (
|
|
jsonEncodingRegex = regexp.MustCompile(`(application|text)\/.*?\+*json.*`)
|
|
multipartEncodingRegex = regexp.MustCompile(`multipart\/.*`)
|
|
urlEncodedEncodingRegex = regexp.MustCompile(`application\/x-www-form-urlencoded.*`)
|
|
)
|
|
|
|
func SerializeRequestBody(_ context.Context, request interface{}, nullable, optional bool, requestFieldName, serializationMethod, tag string) (io.Reader, string, error) {
|
|
bodyReader, contentType, err := serializeRequestBody(request, nullable, optional, requestFieldName, serializationMethod, tag)
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("error serializing request body: %w", err)
|
|
}
|
|
|
|
if bodyReader == nil && !optional {
|
|
return nil, "", fmt.Errorf("request body is required")
|
|
}
|
|
|
|
return bodyReader, contentType, nil
|
|
}
|
|
|
|
func serializeRequestBody(request interface{}, nullable, optional bool, requestFieldName, serializationMethod, tag string) (io.Reader, string, error) {
|
|
requestStructType := reflect.TypeOf(request)
|
|
requestValType := reflect.ValueOf(request)
|
|
|
|
if isNil(requestStructType, requestValType) {
|
|
if !nullable && optional {
|
|
return nil, "", nil
|
|
}
|
|
|
|
return serializeContentType(requestFieldName, SerializationMethodToContentType[serializationMethod], requestValType, tag)
|
|
}
|
|
|
|
if requestStructType.Kind() == reflect.Pointer {
|
|
requestStructType = requestStructType.Elem()
|
|
requestValType = requestValType.Elem()
|
|
}
|
|
|
|
if requestStructType.Kind() != reflect.Struct {
|
|
return serializeContentType(requestFieldName, SerializationMethodToContentType[serializationMethod], requestValType, tag)
|
|
}
|
|
|
|
requestField, ok := requestStructType.FieldByName(requestFieldName)
|
|
|
|
if ok {
|
|
tag := getRequestTag(requestField)
|
|
if tag != nil {
|
|
// request object (non-flattened)
|
|
requestVal := requestValType.FieldByName(requestFieldName)
|
|
val := reflect.ValueOf(requestVal.Interface())
|
|
if isNil(requestField.Type, requestVal) {
|
|
if !nullable && optional {
|
|
return nil, "", nil
|
|
}
|
|
|
|
return serializeContentType(requestFieldName, tag.MediaType, val, string(requestField.Tag))
|
|
}
|
|
|
|
return serializeContentType(requestFieldName, tag.MediaType, val, string(requestField.Tag))
|
|
}
|
|
}
|
|
|
|
// flattened request object
|
|
return serializeContentType(requestFieldName, SerializationMethodToContentType[serializationMethod], reflect.ValueOf(request), tag)
|
|
}
|
|
|
|
func serializeContentType(fieldName string, mediaType string, val reflect.Value, tag string) (io.Reader, string, error) {
|
|
buf := &bytes.Buffer{}
|
|
|
|
if isNil(val.Type(), val) {
|
|
// TODO: what does a null mean for other content types? Just returning an empty buffer for now
|
|
if jsonEncodingRegex.MatchString(mediaType) {
|
|
if _, err := buf.Write([]byte("null")); err != nil {
|
|
return nil, "", err
|
|
}
|
|
}
|
|
|
|
return buf, mediaType, nil
|
|
}
|
|
|
|
switch {
|
|
case jsonEncodingRegex.MatchString(mediaType):
|
|
data, err := MarshalJSON(val.Interface(), reflect.StructTag(tag), true)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
if _, err := buf.Write(data); err != nil {
|
|
return nil, "", err
|
|
}
|
|
case multipartEncodingRegex.MatchString(mediaType):
|
|
var err error
|
|
mediaType, err = encodeMultipartFormData(buf, val.Interface())
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
case urlEncodedEncodingRegex.MatchString(mediaType):
|
|
if err := encodeFormData(fieldName, buf, val.Interface()); err != nil {
|
|
return nil, "", err
|
|
}
|
|
case val.Type().Implements(reflect.TypeOf((*io.Reader)(nil)).Elem()):
|
|
return val.Interface().(io.Reader), mediaType, nil
|
|
default:
|
|
val = reflect.Indirect(val)
|
|
|
|
switch {
|
|
case val.Type().Kind() == reflect.String:
|
|
if _, err := buf.WriteString(valToString(val.Interface())); err != nil {
|
|
return nil, "", err
|
|
}
|
|
case reflect.TypeOf(val.Interface()) == reflect.TypeOf([]byte(nil)):
|
|
if _, err := buf.Write(val.Interface().([]byte)); err != nil {
|
|
return nil, "", err
|
|
}
|
|
default:
|
|
return nil, "", fmt.Errorf("invalid request body type %s for mediaType %s", val.Type(), mediaType)
|
|
}
|
|
}
|
|
|
|
return buf, mediaType, nil
|
|
}
|
|
|
|
func encodeMultipartFormData(w io.Writer, data interface{}) (string, error) {
|
|
requestStructType := reflect.TypeOf(data)
|
|
requestValType := reflect.ValueOf(data)
|
|
|
|
if requestStructType.Kind() == reflect.Pointer {
|
|
requestStructType = requestStructType.Elem()
|
|
requestValType = requestValType.Elem()
|
|
}
|
|
|
|
writer := multipart.NewWriter(w)
|
|
|
|
for i := 0; i < requestStructType.NumField(); i++ {
|
|
field := requestStructType.Field(i)
|
|
fieldType := field.Type
|
|
valType := requestValType.Field(i)
|
|
|
|
if isNil(fieldType, valType) {
|
|
continue
|
|
}
|
|
|
|
if fieldType.Kind() == reflect.Pointer {
|
|
fieldType = fieldType.Elem()
|
|
valType = valType.Elem()
|
|
}
|
|
|
|
tag := parseMultipartFormTag(field)
|
|
if tag.File {
|
|
switch fieldType.Kind() {
|
|
case reflect.Slice, reflect.Array:
|
|
for i := 0; i < valType.Len(); i++ {
|
|
arrayVal := valType.Index(i)
|
|
|
|
if err := encodeMultipartFormDataFile(writer, tag.Name+"[]", arrayVal.Type(), arrayVal); err != nil {
|
|
writer.Close()
|
|
return "", err
|
|
}
|
|
}
|
|
default:
|
|
if err := encodeMultipartFormDataFile(writer, tag.Name, fieldType, valType); err != nil {
|
|
writer.Close()
|
|
return "", err
|
|
}
|
|
}
|
|
} else if tag.JSON {
|
|
jw, err := writer.CreateFormField(tag.Name)
|
|
if err != nil {
|
|
writer.Close()
|
|
return "", err
|
|
}
|
|
d, err := MarshalJSON(valType.Interface(), field.Tag, true)
|
|
if err != nil {
|
|
writer.Close()
|
|
return "", err
|
|
}
|
|
if _, err := jw.Write(d); err != nil {
|
|
writer.Close()
|
|
return "", err
|
|
}
|
|
} else {
|
|
switch fieldType.Kind() {
|
|
case reflect.Slice, reflect.Array:
|
|
values := parseDelimitedArray(true, valType, ",")
|
|
for _, v := range values {
|
|
if err := writer.WriteField(tag.Name, v); err != nil {
|
|
writer.Close()
|
|
return "", err
|
|
}
|
|
}
|
|
default:
|
|
if err := writer.WriteField(tag.Name, valToString(valType.Interface())); err != nil {
|
|
writer.Close()
|
|
return "", err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := writer.Close(); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return writer.FormDataContentType(), nil
|
|
}
|
|
|
|
func encodeMultipartFormDataFile(w *multipart.Writer, fieldName string, fieldType reflect.Type, valType reflect.Value) error {
|
|
if fieldType.Kind() != reflect.Struct {
|
|
return fmt.Errorf("invalid type %s for multipart/form-data file", valType.Type())
|
|
}
|
|
|
|
var fileName string
|
|
var reader io.Reader
|
|
|
|
for i := 0; i < fieldType.NumField(); i++ {
|
|
field := fieldType.Field(i)
|
|
val := valType.Field(i)
|
|
|
|
tag := parseMultipartFormTag(field)
|
|
if !tag.Content && tag.Name == "" {
|
|
continue
|
|
}
|
|
|
|
if tag.Content && val.CanInterface() {
|
|
if reflect.TypeOf(val.Interface()) == reflect.TypeOf([]byte(nil)) {
|
|
reader = bytes.NewReader(val.Interface().([]byte))
|
|
} else if reflect.TypeOf(val.Interface()).Implements(reflect.TypeOf((*io.Reader)(nil)).Elem()) {
|
|
reader = val.Interface().(io.Reader)
|
|
}
|
|
} else {
|
|
fileName = val.String()
|
|
}
|
|
}
|
|
|
|
if fileName == "" || reader == nil {
|
|
return fmt.Errorf("invalid multipart/form-data file")
|
|
}
|
|
|
|
// Detect content type based on file extension
|
|
contentType := mime.TypeByExtension(filepath.Ext(fileName))
|
|
if contentType == "" {
|
|
contentType = "application/octet-stream"
|
|
}
|
|
|
|
// Create multipart header with proper content type
|
|
h := make(textproto.MIMEHeader)
|
|
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, fieldName, fileName))
|
|
h.Set("Content-Type", contentType)
|
|
|
|
fw, err := w.CreatePart(h)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := io.Copy(fw, reader); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Reset seek position to 0 if the reader supports seeking
|
|
if seeker, ok := reader.(io.Seeker); ok {
|
|
_, _ = seeker.Seek(0, io.SeekStart)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func encodeFormData(fieldName string, w io.Writer, data interface{}) error {
|
|
requestType := reflect.TypeOf(data)
|
|
requestValType := reflect.ValueOf(data)
|
|
|
|
if requestType.Kind() == reflect.Pointer {
|
|
requestType = requestType.Elem()
|
|
requestValType = requestValType.Elem()
|
|
}
|
|
|
|
dataValues := url.Values{}
|
|
|
|
switch requestType.Kind() {
|
|
case reflect.Struct:
|
|
for i := 0; i < requestType.NumField(); i++ {
|
|
field := requestType.Field(i)
|
|
fieldType := field.Type
|
|
valType := requestValType.Field(i)
|
|
|
|
if isNil(fieldType, valType) {
|
|
continue
|
|
}
|
|
|
|
if fieldType.Kind() == reflect.Pointer {
|
|
fieldType = fieldType.Elem()
|
|
valType = valType.Elem()
|
|
}
|
|
|
|
tag := parseFormTag(field)
|
|
if tag.JSON {
|
|
data, err := MarshalJSON(valType.Interface(), field.Tag, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dataValues.Set(tag.Name, string(data))
|
|
} else {
|
|
switch tag.Style {
|
|
// TODO: support other styles
|
|
case "form":
|
|
values := populateForm(tag.Name, tag.Explode, fieldType, valType, ",", nil, nil, func(sf reflect.StructField) string {
|
|
tag := parseFormTag(field)
|
|
if tag == nil {
|
|
return ""
|
|
}
|
|
|
|
return tag.Name
|
|
})
|
|
for k, v := range values {
|
|
for _, vv := range v {
|
|
dataValues.Add(k, vv)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
case reflect.Map:
|
|
// check if optionalnullable.OptionalNullable[T]
|
|
if nullableValue, ok := optionalnullable.AsOptionalNullable(requestValType); ok {
|
|
// Handle optionalnullable.OptionalNullable[T] using GetUntyped method
|
|
if value, isSet := nullableValue.GetUntyped(); isSet && value != nil {
|
|
dataValues.Set(fieldName, valToString(value))
|
|
}
|
|
// If not set or explicitly null, skip adding to form
|
|
break
|
|
}
|
|
|
|
// Handle regular map
|
|
for _, k := range requestValType.MapKeys() {
|
|
v := requestValType.MapIndex(k)
|
|
dataValues.Set(fmt.Sprintf("%v", k.Interface()), valToString(v.Interface()))
|
|
}
|
|
case reflect.Slice, reflect.Array:
|
|
for i := 0; i < requestValType.Len(); i++ {
|
|
v := requestValType.Index(i)
|
|
dataValues.Set(fieldName, valToString(v.Interface()))
|
|
}
|
|
}
|
|
|
|
if _, err := w.Write([]byte(dataValues.Encode())); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type requestTag struct {
|
|
MediaType string
|
|
}
|
|
|
|
func getRequestTag(field reflect.StructField) *requestTag {
|
|
// example `request:"mediaType=multipart/form-data"`
|
|
values := parseStructTag(requestTagKey, field)
|
|
if values == nil {
|
|
return nil
|
|
}
|
|
|
|
tag := &requestTag{
|
|
MediaType: "application/octet-stream",
|
|
}
|
|
|
|
for k, v := range values {
|
|
switch k {
|
|
case "mediaType":
|
|
tag.MediaType = v
|
|
}
|
|
}
|
|
|
|
return tag
|
|
}
|
|
|
|
type multipartFormTag struct {
|
|
File bool
|
|
Content bool
|
|
JSON bool
|
|
Name string
|
|
}
|
|
|
|
func parseMultipartFormTag(field reflect.StructField) *multipartFormTag {
|
|
// example `multipartForm:"name=file"`
|
|
values := parseStructTag(multipartFormTagKey, field)
|
|
|
|
tag := &multipartFormTag{}
|
|
|
|
for k, v := range values {
|
|
switch k {
|
|
case "file":
|
|
tag.File = v == "true"
|
|
case "content":
|
|
tag.Content = v == "true"
|
|
case "name":
|
|
tag.Name = v
|
|
case "json":
|
|
tag.JSON = v == "true"
|
|
}
|
|
}
|
|
|
|
return tag
|
|
}
|
|
|
|
type formTag struct {
|
|
Name string
|
|
JSON bool
|
|
Style string
|
|
Explode bool
|
|
}
|
|
|
|
func parseFormTag(field reflect.StructField) *formTag {
|
|
// example `form:"name=propName,style=spaceDelimited,explode"`
|
|
values := parseStructTag(formTagKey, field)
|
|
|
|
tag := &formTag{
|
|
Style: "form",
|
|
Explode: true,
|
|
}
|
|
|
|
for k, v := range values {
|
|
switch k {
|
|
case "name":
|
|
tag.Name = v
|
|
case "json":
|
|
tag.JSON = v == "true"
|
|
case "style":
|
|
tag.Style = v
|
|
case "explode":
|
|
tag.Explode = v == "true"
|
|
}
|
|
}
|
|
|
|
return tag
|
|
}
|