package runtime
import (
"bytes"
"encoding"
"encoding/json"
"errors"
"fmt"
"net/url"
"reflect"
"sort"
"strconv"
"strings"
"time"
"github.com/oapi-codegen/runtime/types"
"github.com/google/uuid"
)
type ParamLocation int
const (
ParamLocationUndefined ParamLocation = iota
ParamLocationQuery
ParamLocationPath
ParamLocationHeader
ParamLocationCookie
)
func StyleParam (style string , explode bool , paramName string , value interface {}) (string , error ) {
return StyleParamWithLocation (style , explode , paramName , ParamLocationUndefined , value )
}
func StyleParamWithLocation (style string , explode bool , paramName string , paramLocation ParamLocation , value interface {}) (string , error ) {
t := reflect .TypeOf (value )
v := reflect .ValueOf (value )
if t .Kind () == reflect .Ptr {
if v .IsNil () {
return "" , fmt .Errorf ("value is a nil pointer" )
}
v = reflect .Indirect (v )
t = v .Type ()
}
if tu , ok := value .(encoding .TextMarshaler ); ok {
t := reflect .Indirect (reflect .ValueOf (value )).Type ()
convertableToTime := t .ConvertibleTo (reflect .TypeOf (time .Time {}))
convertableToDate := t .ConvertibleTo (reflect .TypeOf (types .Date {}))
if !convertableToTime && !convertableToDate {
b , err := tu .MarshalText ()
if err != nil {
return "" , fmt .Errorf ("error marshaling '%s' as text: %s" , value , err )
}
return stylePrimitive (style , explode , paramName , paramLocation , string (b ))
}
}
switch t .Kind () {
case reflect .Slice :
n := v .Len ()
sliceVal := make ([]interface {}, n )
for i := 0 ; i < n ; i ++ {
sliceVal [i ] = v .Index (i ).Interface ()
}
return styleSlice (style , explode , paramName , paramLocation , sliceVal )
case reflect .Struct :
return styleStruct (style , explode , paramName , paramLocation , value )
case reflect .Map :
return styleMap (style , explode , paramName , paramLocation , value )
default :
return stylePrimitive (style , explode , paramName , paramLocation , value )
}
}
func styleSlice (style string , explode bool , paramName string , paramLocation ParamLocation , values []interface {}) (string , error ) {
if style == "deepObject" {
if !explode {
return "" , errors .New ("deepObjects must be exploded" )
}
return MarshalDeepObject (values , paramName )
}
var prefix string
var separator string
switch style {
case "simple" :
separator = ","
case "label" :
prefix = "."
if explode {
separator = "."
} else {
separator = ","
}
case "matrix" :
prefix = fmt .Sprintf (";%s=" , paramName )
if explode {
separator = prefix
} else {
separator = ","
}
case "form" :
prefix = fmt .Sprintf ("%s=" , paramName )
if explode {
separator = "&" + prefix
} else {
separator = ","
}
case "spaceDelimited" :
prefix = fmt .Sprintf ("%s=" , paramName )
if explode {
separator = "&" + prefix
} else {
separator = " "
}
case "pipeDelimited" :
prefix = fmt .Sprintf ("%s=" , paramName )
if explode {
separator = "&" + prefix
} else {
separator = "|"
}
default :
return "" , fmt .Errorf ("unsupported style '%s'" , style )
}
var err error
var part string
parts := make ([]string , len (values ))
for i , v := range values {
part , err = primitiveToString (v )
part = escapeParameterString (part , paramLocation )
parts [i ] = part
if err != nil {
return "" , fmt .Errorf ("error formatting '%s': %s" , paramName , err )
}
}
return prefix + strings .Join (parts , separator ), nil
}
func sortedKeys (strMap map [string ]string ) []string {
keys := make ([]string , len (strMap ))
i := 0
for k := range strMap {
keys [i ] = k
i ++
}
sort .Strings (keys )
return keys
}
func marshalKnownTypes (value interface {}) (string , bool ) {
v := reflect .Indirect (reflect .ValueOf (value ))
t := v .Type ()
if t .ConvertibleTo (reflect .TypeOf (time .Time {})) {
tt := v .Convert (reflect .TypeOf (time .Time {}))
timeVal := tt .Interface ().(time .Time )
return timeVal .Format (time .RFC3339Nano ), true
}
if t .ConvertibleTo (reflect .TypeOf (types .Date {})) {
d := v .Convert (reflect .TypeOf (types .Date {}))
dateVal := d .Interface ().(types .Date )
return dateVal .Format (types .DateFormat ), true
}
if t .ConvertibleTo (reflect .TypeOf (types .UUID {})) {
u := v .Convert (reflect .TypeOf (types .UUID {}))
uuidVal := u .Interface ().(types .UUID )
return uuidVal .String (), true
}
return "" , false
}
func styleStruct (style string , explode bool , paramName string , paramLocation ParamLocation , value interface {}) (string , error ) {
if timeVal , ok := marshalKnownTypes (value ); ok {
styledVal , err := stylePrimitive (style , explode , paramName , paramLocation , timeVal )
if err != nil {
return "" , fmt .Errorf ("failed to style time: %w" , err )
}
return styledVal , nil
}
if style == "deepObject" {
if !explode {
return "" , errors .New ("deepObjects must be exploded" )
}
return MarshalDeepObject (value , paramName )
}
if m , ok := value .(json .Marshaler ); ok {
buf , err := m .MarshalJSON ()
if err != nil {
return "" , fmt .Errorf ("failed to marshal input to JSON: %w" , err )
}
e := json .NewDecoder (bytes .NewReader (buf ))
e .UseNumber ()
var i2 interface {}
err = e .Decode (&i2 )
if err != nil {
return "" , fmt .Errorf ("failed to unmarshal JSON: %w" , err )
}
s , err := StyleParamWithLocation (style , explode , paramName , paramLocation , i2 )
if err != nil {
return "" , fmt .Errorf ("error style JSON structure: %w" , err )
}
return s , nil
}
v := reflect .ValueOf (value )
t := reflect .TypeOf (value )
fieldDict := make (map [string ]string )
for i := 0 ; i < t .NumField (); i ++ {
fieldT := t .Field (i )
tag := fieldT .Tag .Get ("json" )
fieldName := fieldT .Name
if tag != "" {
tagParts := strings .Split (tag , "," )
name := tagParts [0 ]
if name != "" {
fieldName = name
}
}
f := v .Field (i )
if f .Type ().Kind () == reflect .Ptr && f .IsNil () {
continue
}
str , err := primitiveToString (f .Interface ())
if err != nil {
return "" , fmt .Errorf ("error formatting '%s': %s" , paramName , err )
}
fieldDict [fieldName ] = str
}
return processFieldDict (style , explode , paramName , paramLocation , fieldDict )
}
func styleMap (style string , explode bool , paramName string , paramLocation ParamLocation , value interface {}) (string , error ) {
if style == "deepObject" {
if !explode {
return "" , errors .New ("deepObjects must be exploded" )
}
return MarshalDeepObject (value , paramName )
}
dict , ok := value .(map [string ]interface {})
if !ok {
return "" , errors .New ("map not of type map[string]interface{}" )
}
fieldDict := make (map [string ]string )
for fieldName , value := range dict {
str , err := primitiveToString (value )
if err != nil {
return "" , fmt .Errorf ("error formatting '%s': %s" , paramName , err )
}
fieldDict [fieldName ] = str
}
return processFieldDict (style , explode , paramName , paramLocation , fieldDict )
}
func processFieldDict (style string , explode bool , paramName string , paramLocation ParamLocation , fieldDict map [string ]string ) (string , error ) {
var parts []string
if style != "deepObject" {
if explode {
for _ , k := range sortedKeys (fieldDict ) {
v := escapeParameterString (fieldDict [k ], paramLocation )
parts = append (parts , k +"=" +v )
}
} else {
for _ , k := range sortedKeys (fieldDict ) {
v := escapeParameterString (fieldDict [k ], paramLocation )
parts = append (parts , k )
parts = append (parts , v )
}
}
}
var prefix string
var separator string
switch style {
case "simple" :
separator = ","
case "label" :
prefix = "."
if explode {
separator = prefix
} else {
separator = ","
}
case "matrix" :
if explode {
separator = ";"
prefix = ";"
} else {
separator = ","
prefix = fmt .Sprintf (";%s=" , paramName )
}
case "form" :
if explode {
separator = "&"
} else {
prefix = fmt .Sprintf ("%s=" , paramName )
separator = ","
}
case "deepObject" :
{
if !explode {
return "" , fmt .Errorf ("deepObject parameters must be exploded" )
}
for _ , k := range sortedKeys (fieldDict ) {
v := fieldDict [k ]
part := fmt .Sprintf ("%s[%s]=%s" , paramName , k , v )
parts = append (parts , part )
}
separator = "&"
}
default :
return "" , fmt .Errorf ("unsupported style '%s'" , style )
}
return prefix + strings .Join (parts , separator ), nil
}
func stylePrimitive (style string , explode bool , paramName string , paramLocation ParamLocation , value interface {}) (string , error ) {
strVal , err := primitiveToString (value )
if err != nil {
return "" , err
}
var prefix string
switch style {
case "simple" :
case "label" :
prefix = "."
case "matrix" :
prefix = fmt .Sprintf (";%s=" , paramName )
case "form" :
prefix = fmt .Sprintf ("%s=" , paramName )
default :
return "" , fmt .Errorf ("unsupported style '%s'" , style )
}
return prefix + escapeParameterString (strVal , paramLocation ), nil
}
func primitiveToString (value interface {}) (string , error ) {
var output string
if res , ok := marshalKnownTypes (value ); ok {
return res , nil
}
v := reflect .Indirect (reflect .ValueOf (value ))
t := v .Type ()
kind := t .Kind ()
switch kind {
case reflect .Int8 , reflect .Int16 , reflect .Int32 , reflect .Int64 , reflect .Int :
output = strconv .FormatInt (v .Int (), 10 )
case reflect .Uint8 , reflect .Uint16 , reflect .Uint32 , reflect .Uint64 , reflect .Uint :
output = strconv .FormatUint (v .Uint (), 10 )
case reflect .Float64 :
output = strconv .FormatFloat (v .Float (), 'f' , -1 , 64 )
case reflect .Float32 :
output = strconv .FormatFloat (v .Float (), 'f' , -1 , 32 )
case reflect .Bool :
if v .Bool () {
output = "true"
} else {
output = "false"
}
case reflect .String :
output = v .String ()
case reflect .Struct :
if v , ok := value .(uuid .UUID ); ok {
output = v .String ()
break
}
if m , ok := value .(json .Marshaler ); ok {
buf , err := m .MarshalJSON ()
if err != nil {
return "" , fmt .Errorf ("failed to marshal input to JSON: %w" , err )
}
e := json .NewDecoder (bytes .NewReader (buf ))
e .UseNumber ()
var i2 interface {}
err = e .Decode (&i2 )
if err != nil {
return "" , fmt .Errorf ("failed to unmarshal JSON: %w" , err )
}
output , err = primitiveToString (i2 )
if err != nil {
return "" , fmt .Errorf ("error convert JSON structure: %w" , err )
}
break
}
fallthrough
default :
v , ok := value .(fmt .Stringer )
if !ok {
return "" , fmt .Errorf ("unsupported type %s" , reflect .TypeOf (value ).String ())
}
output = v .String ()
}
return output , nil
}
func escapeParameterString (value string , paramLocation ParamLocation ) string {
switch paramLocation {
case ParamLocationQuery :
return url .QueryEscape (value )
case ParamLocationPath :
return url .PathEscape (value )
default :
return value
}
}
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 .