123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473 |
- // Copyright 2019 DeepMap, Inc.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package runtime
- import (
- "bytes"
- "encoding"
- "encoding/json"
- "errors"
- "fmt"
- "net/url"
- "reflect"
- "sort"
- "strconv"
- "strings"
- "time"
- "github.com/deepmap/oapi-codegen/pkg/types"
- )
- // Parameter escaping works differently based on where a header is found
- type ParamLocation int
- const (
- ParamLocationUndefined ParamLocation = iota
- ParamLocationQuery
- ParamLocationPath
- ParamLocationHeader
- ParamLocationCookie
- )
- // StyleParam is used by older generated code, and must remain compatible
- // with that code. It is not to be used in new templates. Please see the
- // function below, which can specialize its output based on the location of
- // the parameter.
- func StyleParam(style string, explode bool, paramName string, value interface{}) (string, error) {
- return StyleParamWithLocation(style, explode, paramName, ParamLocationUndefined, value)
- }
- // Given an input value, such as a primitive type, array or object, turn it
- // into a parameter based on style/explode definition, performing whatever
- // escaping is necessary based on parameter location
- func StyleParamWithLocation(style string, explode bool, paramName string, paramLocation ParamLocation, value interface{}) (string, error) {
- t := reflect.TypeOf(value)
- v := reflect.ValueOf(value)
- // Things may be passed in by pointer, we need to dereference, so return
- // error on nil.
- if t.Kind() == reflect.Ptr {
- if v.IsNil() {
- return "", fmt.Errorf("value is a nil pointer")
- }
- v = reflect.Indirect(v)
- t = v.Type()
- }
- // If the value implements encoding.TextMarshaler we use it for marshaling
- // https://github.com/deepmap/oapi-codegen/issues/504
- 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{}))
- // Since both time.Time and types.Date implement encoding.TextMarshaler
- // we should avoid calling theirs MarshalText()
- 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)
- }
- // We're going to assume here that the array is one of simple types.
- 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
- }
- // These are special cases. The value may be a date, time, or uuid,
- // in which case, marshal it into the correct format.
- 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 input has Marshaler, such as object has Additional Property or AnyOf,
- // We use this Marshaler and convert into interface{} before styling.
- 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
- }
- // Otherwise, we need to build a dictionary of the struct's fields. Each
- // field may only be a primitive value.
- v := reflect.ValueOf(value)
- t := reflect.TypeOf(value)
- fieldDict := make(map[string]string)
- for i := 0; i < t.NumField(); i++ {
- fieldT := t.Field(i)
- // Find the json annotation on the field, and use the json specified
- // name if available, otherwise, just the field name.
- 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)
- // Unset optional fields will be nil pointers, skip over those.
- 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
- // This works for everything except deepObject. We'll handle that one
- // separately.
- 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
- }
- // Converts a primitive value to a string. We need to do this based on the
- // Kind of an interface, not the Type to work with aliased types.
- func primitiveToString(value interface{}) (string, error) {
- var output string
- // sometimes time and date used like primitive types
- // it can happen if paramether is object and has time or date as field
- if res, ok := marshalKnownTypes(value); ok {
- return res, nil
- }
- // Values may come in by pointer for optionals, so make sure to dereferene.
- 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 input has Marshaler, such as object has Additional Property or AnyOf,
- // We use this Marshaler and convert into interface{} before styling.
- 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
- }
- // This function escapes a parameter value bas on the location of that parameter.
- // Query params and path params need different kinds of escaping, while header
- // and cookie params seem not to need escaping.
- func escapeParameterString(value string, paramLocation ParamLocation) string {
- switch paramLocation {
- case ParamLocationQuery:
- return url.QueryEscape(value)
- case ParamLocationPath:
- return url.PathEscape(value)
- default:
- return value
- }
- }
|