Files
plexgo/internal/utils/requestbody.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, 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
}