// 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 }