// 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 ( ) // 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 ( string, bool, string, interface{}) (string, error) { return StyleParamWithLocation(, , , ParamLocationUndefined, ) } // 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 ( string, bool, string, ParamLocation, interface{}) (string, error) { := reflect.TypeOf() := reflect.ValueOf() // Things may be passed in by pointer, we need to dereference, so return // error on nil. if .Kind() == reflect.Ptr { if .IsNil() { return "", fmt.Errorf("value is a nil pointer") } = reflect.Indirect() = .Type() } // If the value implements encoding.TextMarshaler we use it for marshaling // https://github.com/deepmap/oapi-codegen/issues/504 if , := .(encoding.TextMarshaler); { := reflect.Indirect(reflect.ValueOf()).Type() := .ConvertibleTo(reflect.TypeOf(time.Time{})) := .ConvertibleTo(reflect.TypeOf(types.Date{})) // Since both time.Time and types.Date implement encoding.TextMarshaler // we should avoid calling theirs MarshalText() if ! && ! { , := .MarshalText() if != nil { return "", fmt.Errorf("error marshaling '%s' as text: %s", , ) } return stylePrimitive(, , , , string()) } } switch .Kind() { case reflect.Slice: := .Len() := make([]interface{}, ) for := 0; < ; ++ { [] = .Index().Interface() } return styleSlice(, , , , ) case reflect.Struct: return styleStruct(, , , , ) case reflect.Map: return styleMap(, , , , ) default: return stylePrimitive(, , , , ) } } func ( string, bool, string, ParamLocation, []interface{}) (string, error) { if == "deepObject" { if ! { return "", errors.New("deepObjects must be exploded") } return MarshalDeepObject(, ) } var string var string switch { case "simple": = "," case "label": = "." if { = "." } else { = "," } case "matrix": = fmt.Sprintf(";%s=", ) if { = } else { = "," } case "form": = fmt.Sprintf("%s=", ) if { = "&" + } else { = "," } case "spaceDelimited": = fmt.Sprintf("%s=", ) if { = "&" + } else { = " " } case "pipeDelimited": = fmt.Sprintf("%s=", ) if { = "&" + } else { = "|" } default: return "", fmt.Errorf("unsupported style '%s'", ) } // We're going to assume here that the array is one of simple types. var error var string := make([]string, len()) for , := range { , = primitiveToString() = escapeParameterString(, ) [] = if != nil { return "", fmt.Errorf("error formatting '%s': %s", , ) } } return + strings.Join(, ), nil } func ( map[string]string) []string { := make([]string, len()) := 0 for := range { [] = ++ } sort.Strings() return } // These are special cases. The value may be a date, time, or uuid, // in which case, marshal it into the correct format. func ( interface{}) (string, bool) { := reflect.Indirect(reflect.ValueOf()) := .Type() if .ConvertibleTo(reflect.TypeOf(time.Time{})) { := .Convert(reflect.TypeOf(time.Time{})) := .Interface().(time.Time) return .Format(time.RFC3339Nano), true } if .ConvertibleTo(reflect.TypeOf(types.Date{})) { := .Convert(reflect.TypeOf(types.Date{})) := .Interface().(types.Date) return .Format(types.DateFormat), true } if .ConvertibleTo(reflect.TypeOf(types.UUID{})) { := .Convert(reflect.TypeOf(types.UUID{})) := .Interface().(types.UUID) return .String(), true } return "", false } func ( string, bool, string, ParamLocation, interface{}) (string, error) { if , := marshalKnownTypes(); { , := stylePrimitive(, , , , ) if != nil { return "", fmt.Errorf("failed to style time: %w", ) } return , nil } if == "deepObject" { if ! { return "", errors.New("deepObjects must be exploded") } return MarshalDeepObject(, ) } // If input has Marshaler, such as object has Additional Property or AnyOf, // We use this Marshaler and convert into interface{} before styling. if , := .(json.Marshaler); { , := .MarshalJSON() if != nil { return "", fmt.Errorf("failed to marshal input to JSON: %w", ) } := json.NewDecoder(bytes.NewReader()) .UseNumber() var interface{} = .Decode(&) if != nil { return "", fmt.Errorf("failed to unmarshal JSON: %w", ) } , := StyleParamWithLocation(, , , , ) if != nil { return "", fmt.Errorf("error style JSON structure: %w", ) } return , nil } // Otherwise, we need to build a dictionary of the struct's fields. Each // field may only be a primitive value. := reflect.ValueOf() := reflect.TypeOf() := make(map[string]string) for := 0; < .NumField(); ++ { := .Field() // Find the json annotation on the field, and use the json specified // name if available, otherwise, just the field name. := .Tag.Get("json") := .Name if != "" { := strings.Split(, ",") := [0] if != "" { = } } := .Field() // Unset optional fields will be nil pointers, skip over those. if .Type().Kind() == reflect.Ptr && .IsNil() { continue } , := primitiveToString(.Interface()) if != nil { return "", fmt.Errorf("error formatting '%s': %s", , ) } [] = } return processFieldDict(, , , , ) } func ( string, bool, string, ParamLocation, interface{}) (string, error) { if == "deepObject" { if ! { return "", errors.New("deepObjects must be exploded") } return MarshalDeepObject(, ) } , := .(map[string]interface{}) if ! { return "", errors.New("map not of type map[string]interface{}") } := make(map[string]string) for , := range { , := primitiveToString() if != nil { return "", fmt.Errorf("error formatting '%s': %s", , ) } [] = } return processFieldDict(, , , , ) } func ( string, bool, string, ParamLocation, map[string]string) (string, error) { var []string // This works for everything except deepObject. We'll handle that one // separately. if != "deepObject" { if { for , := range sortedKeys() { := escapeParameterString([], ) = append(, +"="+) } } else { for , := range sortedKeys() { := escapeParameterString([], ) = append(, ) = append(, ) } } } var string var string switch { case "simple": = "," case "label": = "." if { = } else { = "," } case "matrix": if { = ";" = ";" } else { = "," = fmt.Sprintf(";%s=", ) } case "form": if { = "&" } else { = fmt.Sprintf("%s=", ) = "," } case "deepObject": { if ! { return "", fmt.Errorf("deepObject parameters must be exploded") } for , := range sortedKeys() { := [] := fmt.Sprintf("%s[%s]=%s", , , ) = append(, ) } = "&" } default: return "", fmt.Errorf("unsupported style '%s'", ) } return + strings.Join(, ), nil } func ( string, bool, string, ParamLocation, interface{}) (string, error) { , := primitiveToString() if != nil { return "", } var string switch { case "simple": case "label": = "." case "matrix": = fmt.Sprintf(";%s=", ) case "form": = fmt.Sprintf("%s=", ) default: return "", fmt.Errorf("unsupported style '%s'", ) } return + escapeParameterString(, ), 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 ( interface{}) (string, error) { var string // sometimes time and date used like primitive types // it can happen if paramether is object and has time or date as field if , := marshalKnownTypes(); { return , nil } // Values may come in by pointer for optionals, so make sure to dereferene. := reflect.Indirect(reflect.ValueOf()) := .Type() := .Kind() switch { case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: = strconv.FormatInt(.Int(), 10) case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: = strconv.FormatUint(.Uint(), 10) case reflect.Float64: = strconv.FormatFloat(.Float(), 'f', -1, 64) case reflect.Float32: = strconv.FormatFloat(.Float(), 'f', -1, 32) case reflect.Bool: if .Bool() { = "true" } else { = "false" } case reflect.String: = .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 , := .(uuid.UUID); { = .String() break } if , := .(json.Marshaler); { , := .MarshalJSON() if != nil { return "", fmt.Errorf("failed to marshal input to JSON: %w", ) } := json.NewDecoder(bytes.NewReader()) .UseNumber() var interface{} = .Decode(&) if != nil { return "", fmt.Errorf("failed to unmarshal JSON: %w", ) } , = () if != nil { return "", fmt.Errorf("error convert JSON structure: %w", ) } break } fallthrough default: , := .(fmt.Stringer) if ! { return "", fmt.Errorf("unsupported type %s", reflect.TypeOf().String()) } = .String() } return , nil } // escapeParameterString 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 ( string, ParamLocation) string { switch { case ParamLocationQuery: return url.QueryEscape() case ParamLocationPath: return url.PathEscape() default: return } }