package runtime
import (
"encoding/json"
"errors"
"fmt"
"mime/multipart"
"net/url"
"reflect"
"strconv"
"strings"
"github.com/oapi-codegen/runtime/types"
)
const tagName = "json"
const jsonContentType = "application/json"
type RequestBodyEncoding struct {
ContentType string
Style string
Explode *bool
Required *bool
}
func BindMultipart (ptr interface {}, reader multipart .Reader ) error {
const defaultMemory = 32 << 20
form , err := reader .ReadForm (defaultMemory )
if err != nil {
return err
}
return BindForm (ptr , form .Value , form .File , nil )
}
func BindForm (ptr interface {}, form map [string ][]string , files map [string ][]*multipart .FileHeader , encodings map [string ]RequestBodyEncoding ) error {
ptrVal := reflect .Indirect (reflect .ValueOf (ptr ))
if ptrVal .Kind () != reflect .Struct {
return errors .New ("form data body should be a struct" )
}
tValue := ptrVal .Type ()
for i := 0 ; i < tValue .NumField (); i ++ {
field := ptrVal .Field (i )
tag := tValue .Field (i ).Tag .Get (tagName )
if !field .CanInterface () || tag == "-" {
continue
}
tag = strings .Split (tag , "," )[0 ]
if encoding , ok := encodings [tag ]; ok {
values := form [tag ]
if len (values ) == 0 {
continue
}
value := values [0 ]
if encoding .ContentType != "" {
if strings .HasPrefix (encoding .ContentType , jsonContentType ) {
if err := json .Unmarshal ([]byte (value ), ptr ); err != nil {
return err
}
}
return errors .New ("unsupported encoding, only application/json is supported" )
} else {
var explode bool
if encoding .Explode != nil {
explode = *encoding .Explode
}
var required bool
if encoding .Required != nil {
required = *encoding .Required
}
if err := BindStyledParameterWithOptions (encoding .Style , tag , value , field .Addr ().Interface (), BindStyledParameterOptions {
ParamLocation : ParamLocationUndefined ,
Explode : explode ,
Required : required ,
}); err != nil {
return err
}
}
} else {
if _ , err := bindFormImpl (field , form , files , tag ); err != nil {
return err
}
}
}
return nil
}
func MarshalForm (ptr interface {}, encodings map [string ]RequestBodyEncoding ) (url .Values , error ) {
ptrVal := reflect .Indirect (reflect .ValueOf (ptr ))
if ptrVal .Kind () != reflect .Struct {
return nil , errors .New ("form data body should be a struct" )
}
tValue := ptrVal .Type ()
result := make (url .Values )
for i := 0 ; i < tValue .NumField (); i ++ {
field := ptrVal .Field (i )
tag := tValue .Field (i ).Tag .Get (tagName )
if !field .CanInterface () || tag == "-" {
continue
}
omitEmpty := strings .HasSuffix (tag , ",omitempty" )
if omitEmpty && field .IsZero () {
continue
}
tag = strings .Split (tag , "," )[0 ]
if encoding , ok := encodings [tag ]; ok && encoding .ContentType != "" {
if strings .HasPrefix (encoding .ContentType , jsonContentType ) {
if data , err := json .Marshal (field ); err != nil {
return nil , err
} else {
result [tag ] = append (result [tag ], string (data ))
}
}
return nil , errors .New ("unsupported encoding, only application/json is supported" )
} else {
marshalFormImpl (field , result , tag )
}
}
return result , nil
}
func bindFormImpl (v reflect .Value , form map [string ][]string , files map [string ][]*multipart .FileHeader , name string ) (bool , error ) {
var hasData bool
switch v .Kind () {
case reflect .Interface :
return bindFormImpl (v .Elem (), form , files , name )
case reflect .Ptr :
ptrData := v .Elem ()
if !ptrData .IsValid () {
ptrData = reflect .New (v .Type ().Elem ())
}
ptrHasData , err := bindFormImpl (ptrData , form , files , name )
if err == nil && ptrHasData && !v .Elem ().IsValid () {
v .Set (ptrData )
}
return ptrHasData , err
case reflect .Slice :
if files := append (files [name ], files [name +"[]" ]...); len (files ) != 0 {
if _ , ok := v .Interface ().([]types .File ); ok {
result := make ([]types .File , len (files ))
for i , file := range files {
result [i ].InitFromMultipart (file )
}
v .Set (reflect .ValueOf (result ))
hasData = true
}
}
indexedElementsCount := indexedElementsCount (form , files , name )
items := append (form [name ], form [name +"[]" ]...)
if indexedElementsCount +len (items ) != 0 {
result := reflect .MakeSlice (v .Type (), indexedElementsCount +len (items ), indexedElementsCount +len (items ))
for i := 0 ; i < indexedElementsCount ; i ++ {
if _ , err := bindFormImpl (result .Index (i ), form , files , fmt .Sprintf ("%s[%v]" , name , i )); err != nil {
return false , err
}
}
for i , item := range items {
if err := BindStringToObject (item , result .Index (indexedElementsCount +i ).Addr ().Interface ()); err != nil {
return false , err
}
}
v .Set (result )
hasData = true
}
case reflect .Struct :
if files := files [name ]; len (files ) != 0 {
if file , ok := v .Interface ().(types .File ); ok {
file .InitFromMultipart (files [0 ])
v .Set (reflect .ValueOf (file ))
return true , nil
}
}
for i := 0 ; i < v .NumField (); i ++ {
field := v .Type ().Field (i )
tag := field .Tag .Get (tagName )
if field .Name == "AdditionalProperties" && field .Type .Kind () == reflect .Map && tag == "-" {
additionalPropertiesHasData , err := bindAdditionalProperties (v .Field (i ), v , form , files , name )
if err != nil {
return false , err
}
hasData = hasData || additionalPropertiesHasData
}
if !v .Field (i ).CanInterface () || tag == "-" {
continue
}
tag = strings .Split (tag , "," )[0 ]
fieldHasData , err := bindFormImpl (v .Field (i ), form , files , fmt .Sprintf ("%s[%s]" , name , tag ))
if err != nil {
return false , err
}
hasData = hasData || fieldHasData
}
return hasData , nil
default :
value := form [name ]
if len (value ) != 0 {
return true , BindStringToObject (value [0 ], v .Addr ().Interface ())
}
}
return hasData , nil
}
func indexedElementsCount (form map [string ][]string , files map [string ][]*multipart .FileHeader , name string ) int {
name += "["
maxIndex := -1
for k := range form {
if strings .HasPrefix (k , name ) {
str := strings .TrimPrefix (k , name )
str = str [:strings .Index (str , "]" )]
if idx , err := strconv .Atoi (str ); err == nil {
if idx > maxIndex {
maxIndex = idx
}
}
}
}
for k := range files {
if strings .HasPrefix (k , name ) {
str := strings .TrimPrefix (k , name )
str = str [:strings .Index (str , "]" )]
if idx , err := strconv .Atoi (str ); err == nil {
if idx > maxIndex {
maxIndex = idx
}
}
}
}
return maxIndex + 1
}
func bindAdditionalProperties (additionalProperties reflect .Value , parentStruct reflect .Value , form map [string ][]string , files map [string ][]*multipart .FileHeader , name string ) (bool , error ) {
hasData := false
valueType := additionalProperties .Type ().Elem ()
fieldsSet := make (map [string ]struct {})
for i := 0 ; i < parentStruct .NumField (); i ++ {
tag := parentStruct .Type ().Field (i ).Tag .Get (tagName )
if !parentStruct .Field (i ).CanInterface () || tag == "-" {
continue
}
tag = strings .Split (tag , "," )[0 ]
fieldsSet [tag ] = struct {}{}
}
result := reflect .MakeMap (additionalProperties .Type ())
for k := range form {
if strings .HasPrefix (k , name +"[" ) {
key := strings .TrimPrefix (k , name +"[" )
key = key [:strings .Index (key , "]" )]
if _ , ok := fieldsSet [key ]; ok {
continue
}
value := reflect .New (valueType )
ptrHasData , err := bindFormImpl (value , form , files , fmt .Sprintf ("%s[%s]" , name , key ))
if err != nil {
return false , err
}
result .SetMapIndex (reflect .ValueOf (key ), value .Elem ())
hasData = hasData || ptrHasData
}
}
for k := range files {
if strings .HasPrefix (k , name +"[" ) {
key := strings .TrimPrefix (k , name +"[" )
key = key [:strings .Index (key , "]" )]
if _ , ok := fieldsSet [key ]; ok {
continue
}
value := reflect .New (valueType )
result .SetMapIndex (reflect .ValueOf (key ), value )
ptrHasData , err := bindFormImpl (value , form , files , fmt .Sprintf ("%s[%s]" , name , key ))
if err != nil {
return false , err
}
result .SetMapIndex (reflect .ValueOf (key ), value .Elem ())
hasData = hasData || ptrHasData
}
}
if hasData {
additionalProperties .Set (result )
}
return hasData , nil
}
func marshalFormImpl (v reflect .Value , result url .Values , name string ) {
switch v .Kind () {
case reflect .Interface , reflect .Ptr :
marshalFormImpl (v .Elem (), result , name )
case reflect .Slice :
for i := 0 ; i < v .Len (); i ++ {
elem := v .Index (i )
marshalFormImpl (elem , result , fmt .Sprintf ("%s[%v]" , name , i ))
}
case reflect .Struct :
for i := 0 ; i < v .NumField (); i ++ {
field := v .Type ().Field (i )
tag := field .Tag .Get (tagName )
if field .Name == "AdditionalProperties" && tag == "-" {
iter := v .MapRange ()
for iter .Next () {
marshalFormImpl (iter .Value (), result , fmt .Sprintf ("%s[%s]" , name , iter .Key ().String ()))
}
continue
}
if !v .Field (i ).CanInterface () || tag == "-" {
continue
}
tag = strings .Split (tag , "," )[0 ]
marshalFormImpl (v .Field (i ), result , fmt .Sprintf ("%s[%s]" , name , tag ))
}
default :
result [name ] = append (result [name ], fmt .Sprint (v .Interface ()))
}
}
The pages are generated with Golds v0.7.6 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .