123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526 |
- // 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 (
- "encoding"
- "encoding/json"
- "errors"
- "fmt"
- "net/url"
- "reflect"
- "strings"
- "time"
- "github.com/deepmap/oapi-codegen/pkg/types"
- )
- // BindStyledParameter binds a parameter as described in the Path Parameters
- // section here to a Go object:
- // https://swagger.io/docs/specification/serialization/
- // It is a backward compatible function to clients generated with codegen
- // up to version v1.5.5. v1.5.6+ calls the function below.
- func BindStyledParameter(style string, explode bool, paramName string,
- value string, dest interface{}) error {
- return BindStyledParameterWithLocation(style, explode, paramName, ParamLocationUndefined, value, dest)
- }
- // BindStyledParameterWithLocation binds a parameter as described in the Path Parameters
- // section here to a Go object:
- // https://swagger.io/docs/specification/serialization/
- func BindStyledParameterWithLocation(style string, explode bool, paramName string,
- paramLocation ParamLocation, value string, dest interface{}) error {
- if value == "" {
- return fmt.Errorf("parameter '%s' is empty, can't bind its value", paramName)
- }
- // Based on the location of the parameter, we need to unescape it properly.
- var err error
- switch paramLocation {
- case ParamLocationQuery, ParamLocationUndefined:
- // We unescape undefined parameter locations here for older generated code,
- // since prior to this refactoring, they always query unescaped.
- value, err = url.QueryUnescape(value)
- if err != nil {
- return fmt.Errorf("error unescaping query parameter '%s': %v", paramName, err)
- }
- case ParamLocationPath:
- value, err = url.PathUnescape(value)
- if err != nil {
- return fmt.Errorf("error unescaping path parameter '%s': %v", paramName, err)
- }
- default:
- // Headers and cookies aren't escaped.
- }
- // If the destination implements encoding.TextUnmarshaler we use it for binding
- if tu, ok := dest.(encoding.TextUnmarshaler); ok {
- if err := tu.UnmarshalText([]byte(value)); err != nil {
- return fmt.Errorf("error unmarshalling '%s' text as %T: %s", value, dest, err)
- }
- return nil
- }
- // Everything comes in by pointer, dereference it
- v := reflect.Indirect(reflect.ValueOf(dest))
- // This is the basic type of the destination object.
- t := v.Type()
- if t.Kind() == reflect.Struct {
- // We've got a destination object, we'll create a JSON representation
- // of the input value, and let the json library deal with the unmarshalling
- parts, err := splitStyledParameter(style, explode, true, paramName, value)
- if err != nil {
- return err
- }
- return bindSplitPartsToDestinationStruct(paramName, parts, explode, dest)
- }
- if t.Kind() == reflect.Slice {
- // Chop up the parameter into parts based on its style
- parts, err := splitStyledParameter(style, explode, false, paramName, value)
- if err != nil {
- return fmt.Errorf("error splitting input '%s' into parts: %s", value, err)
- }
- return bindSplitPartsToDestinationArray(parts, dest)
- }
- // Try to bind the remaining types as a base type.
- return BindStringToObject(value, dest)
- }
- // This is a complex set of operations, but each given parameter style can be
- // packed together in multiple ways, using different styles of separators, and
- // different packing strategies based on the explode flag. This function takes
- // as input any parameter format, and unpacks it to a simple list of strings
- // or key-values which we can then treat generically.
- // Why, oh why, great Swagger gods, did you have to make this so complicated?
- func splitStyledParameter(style string, explode bool, object bool, paramName string, value string) ([]string, error) {
- switch style {
- case "simple":
- // In the simple case, we always split on comma
- parts := strings.Split(value, ",")
- return parts, nil
- case "label":
- // In the label case, it's more tricky. In the no explode case, we have
- // /users/.3,4,5 for arrays
- // /users/.role,admin,firstName,Alex for objects
- // in the explode case, we have:
- // /users/.3.4.5
- // /users/.role=admin.firstName=Alex
- if explode {
- // In the exploded case, split everything on periods.
- parts := strings.Split(value, ".")
- // The first part should be an empty string because we have a
- // leading period.
- if parts[0] != "" {
- return nil, fmt.Errorf("invalid format for label parameter '%s', should start with '.'", paramName)
- }
- return parts[1:], nil
- } else {
- // In the unexploded case, we strip off the leading period.
- if value[0] != '.' {
- return nil, fmt.Errorf("invalid format for label parameter '%s', should start with '.'", paramName)
- }
- // The rest is comma separated.
- return strings.Split(value[1:], ","), nil
- }
- case "matrix":
- if explode {
- // In the exploded case, we break everything up on semicolon
- parts := strings.Split(value, ";")
- // The first part should always be empty string, since we started
- // with ;something
- if parts[0] != "" {
- return nil, fmt.Errorf("invalid format for matrix parameter '%s', should start with ';'", paramName)
- }
- parts = parts[1:]
- // Now, if we have an object, we just have a list of x=y statements.
- // for a non-object, like an array, we have id=x, id=y. id=z, etc,
- // so we need to strip the prefix from each of them.
- if !object {
- prefix := paramName + "="
- for i := range parts {
- parts[i] = strings.TrimPrefix(parts[i], prefix)
- }
- }
- return parts, nil
- } else {
- // In the unexploded case, parameters will start with ;paramName=
- prefix := ";" + paramName + "="
- if !strings.HasPrefix(value, prefix) {
- return nil, fmt.Errorf("expected parameter '%s' to start with %s", paramName, prefix)
- }
- str := strings.TrimPrefix(value, prefix)
- return strings.Split(str, ","), nil
- }
- case "form":
- var parts []string
- if explode {
- parts = strings.Split(value, "&")
- if !object {
- prefix := paramName + "="
- for i := range parts {
- parts[i] = strings.TrimPrefix(parts[i], prefix)
- }
- }
- return parts, nil
- } else {
- parts = strings.Split(value, ",")
- prefix := paramName + "="
- for i := range parts {
- parts[i] = strings.TrimPrefix(parts[i], prefix)
- }
- }
- return parts, nil
- }
- return nil, fmt.Errorf("unhandled parameter style: %s", style)
- }
- // Given a set of values as a slice, create a slice to hold them all, and
- // assign to each one by one.
- func bindSplitPartsToDestinationArray(parts []string, dest interface{}) error {
- // Everything comes in by pointer, dereference it
- v := reflect.Indirect(reflect.ValueOf(dest))
- // This is the basic type of the destination object.
- t := v.Type()
- // We've got a destination array, bind each object one by one.
- // This generates a slice of the correct element type and length to
- // hold all the parts.
- newArray := reflect.MakeSlice(t, len(parts), len(parts))
- for i, p := range parts {
- err := BindStringToObject(p, newArray.Index(i).Addr().Interface())
- if err != nil {
- return fmt.Errorf("error setting array element: %s", err)
- }
- }
- v.Set(newArray)
- return nil
- }
- // Given a set of chopped up parameter parts, bind them to a destination
- // struct. The exploded parameter controls whether we send key value pairs
- // in the exploded case, or a sequence of values which are interpreted as
- // tuples.
- // Given the struct Id { firstName string, role string }, as in the canonical
- // swagger examples, in the exploded case, we would pass
- // ["firstName=Alex", "role=admin"], where in the non-exploded case, we would
- // pass "firstName", "Alex", "role", "admin"]
- //
- // We punt the hard work of binding these values to the object to the json
- // library. We'll turn those arrays into JSON strings, and unmarshal
- // into the struct.
- func bindSplitPartsToDestinationStruct(paramName string, parts []string, explode bool, dest interface{}) error {
- // We've got a destination object, we'll create a JSON representation
- // of the input value, and let the json library deal with the unmarshalling
- var fields []string
- if explode {
- fields = make([]string, len(parts))
- for i, property := range parts {
- propertyParts := strings.Split(property, "=")
- if len(propertyParts) != 2 {
- return fmt.Errorf("parameter '%s' has invalid exploded format", paramName)
- }
- fields[i] = "\"" + propertyParts[0] + "\":\"" + propertyParts[1] + "\""
- }
- } else {
- if len(parts)%2 != 0 {
- return fmt.Errorf("parameter '%s' has invalid format, property/values need to be pairs", paramName)
- }
- fields = make([]string, len(parts)/2)
- for i := 0; i < len(parts); i += 2 {
- key := parts[i]
- value := parts[i+1]
- fields[i/2] = "\"" + key + "\":\"" + value + "\""
- }
- }
- jsonParam := "{" + strings.Join(fields, ",") + "}"
- err := json.Unmarshal([]byte(jsonParam), dest)
- if err != nil {
- return fmt.Errorf("error binding parameter %s fields: %s", paramName, err)
- }
- return nil
- }
- // BindQueryParameter works much like BindStyledParameter, however it takes a query argument
- // input array from the url package, since query arguments come through a
- // different path than the styled arguments. They're also exceptionally fussy.
- // For example, consider the exploded and unexploded form parameter examples:
- // (exploded) /users?role=admin&firstName=Alex
- // (unexploded) /users?id=role,admin,firstName,Alex
- //
- // In the first case, we can pull the "id" parameter off the context,
- // and unmarshal via json as an intermediate. Easy. In the second case, we
- // don't have the id QueryParam present, but must find "role", and "firstName".
- // what if there is another parameter similar to "ID" named "role"? We can't
- // tell them apart. This code tries to fail, but the moral of the story is that
- // you shouldn't pass objects via form styled query arguments, just use
- // the Content parameter form.
- func BindQueryParameter(style string, explode bool, required bool, paramName string,
- queryParams url.Values, dest interface{}) error {
- // dv = destination value.
- dv := reflect.Indirect(reflect.ValueOf(dest))
- // intermediate value form which is either dv or dv dereferenced.
- v := dv
- // inner code will bind the string's value to this interface.
- var output interface{}
- if required {
- // If the parameter is required, then the generated code will pass us
- // a pointer to it: &int, &object, and so forth. We can directly set
- // them.
- output = dest
- } else {
- // For optional parameters, we have an extra indirect. An optional
- // parameter of type "int" will be *int on the struct. We pass that
- // in by pointer, and have **int.
- // If the destination, is a nil pointer, we need to allocate it.
- if v.IsNil() {
- t := v.Type()
- newValue := reflect.New(t.Elem())
- // for now, hang onto the output buffer separately from destination,
- // as we don't want to write anything to destination until we can
- // unmarshal successfully, and check whether a field is required.
- output = newValue.Interface()
- } else {
- // If the destination isn't nil, just use that.
- output = v.Interface()
- }
- // Get rid of that extra indirect as compared to the required case,
- // so the code below doesn't have to care.
- v = reflect.Indirect(reflect.ValueOf(output))
- }
- // This is the basic type of the destination object.
- t := v.Type()
- k := t.Kind()
- switch style {
- case "form":
- var parts []string
- if explode {
- // ok, the explode case in query arguments is very, very annoying,
- // because an exploded object, such as /users?role=admin&firstName=Alex
- // isn't actually present in the parameter array. We have to do
- // different things based on destination type.
- values, found := queryParams[paramName]
- var err error
- switch k {
- case reflect.Slice:
- // In the slice case, we simply use the arguments provided by
- // http library.
- if !found {
- if required {
- return fmt.Errorf("query parameter '%s' is required", paramName)
- } else {
- // If an optional parameter is not found, we do nothing,
- return nil
- }
- }
- err = bindSplitPartsToDestinationArray(values, output)
- case reflect.Struct:
- // This case is really annoying, and error prone, but the
- // form style object binding doesn't tell us which arguments
- // in the query string correspond to the object's fields. We'll
- // try to bind field by field.
- var fieldsPresent bool
- fieldsPresent, err = bindParamsToExplodedObject(paramName, queryParams, output)
- // If no fields were set, and there is no error, we will not fall
- // through to assign the destination.
- if !fieldsPresent {
- return nil
- }
- default:
- // Primitive object case. We expect to have 1 value to
- // unmarshal.
- if len(values) == 0 {
- if required {
- return fmt.Errorf("query parameter '%s' is required", paramName)
- } else {
- return nil
- }
- }
- if len(values) != 1 {
- return fmt.Errorf("multiple values for single value parameter '%s'", paramName)
- }
- if !found {
- if required {
- return fmt.Errorf("query parameter '%s' is required", paramName)
- } else {
- // If an optional parameter is not found, we do nothing,
- return nil
- }
- }
- err = BindStringToObject(values[0], output)
- }
- if err != nil {
- return err
- }
- // If the parameter is required, and we've successfully unmarshaled
- // it, this assigns the new object to the pointer pointer.
- if !required {
- dv.Set(reflect.ValueOf(output))
- }
- return nil
- } else {
- values, found := queryParams[paramName]
- if !found {
- if required {
- return fmt.Errorf("query parameter '%s' is required", paramName)
- } else {
- return nil
- }
- }
- if len(values) != 1 {
- return fmt.Errorf("parameter '%s' is not exploded, but is specified multiple times", paramName)
- }
- parts = strings.Split(values[0], ",")
- }
- var err error
- switch k {
- case reflect.Slice:
- err = bindSplitPartsToDestinationArray(parts, output)
- case reflect.Struct:
- err = bindSplitPartsToDestinationStruct(paramName, parts, explode, output)
- default:
- if len(parts) == 0 {
- if required {
- return fmt.Errorf("query parameter '%s' is required", paramName)
- } else {
- return nil
- }
- }
- if len(parts) != 1 {
- return fmt.Errorf("multiple values for single value parameter '%s'", paramName)
- }
- err = BindStringToObject(parts[0], output)
- }
- if err != nil {
- return err
- }
- if !required {
- dv.Set(reflect.ValueOf(output))
- }
- return nil
- case "deepObject":
- if !explode {
- return errors.New("deepObjects must be exploded")
- }
- return UnmarshalDeepObject(dest, paramName, queryParams)
- case "spaceDelimited", "pipeDelimited":
- return fmt.Errorf("query arguments of style '%s' aren't yet supported", style)
- default:
- return fmt.Errorf("style '%s' on parameter '%s' is invalid", style, paramName)
- }
- }
- // bindParamsToExplodedObject reflects the destination structure, and pulls the value for
- // each settable field from the given parameters map. This is to deal with the
- // exploded form styled object which may occupy any number of parameter names.
- // We don't try to be smart here, if the field exists as a query argument,
- // set its value. This function returns a boolean, telling us whether there was
- // anything to bind. There will be nothing to bind if a parameter isn't found by name,
- // or none of an exploded object's fields are present.
- func bindParamsToExplodedObject(paramName string, values url.Values, dest interface{}) (bool, error) {
- // Dereference pointers to their destination values
- binder, v, t := indirect(dest)
- if binder != nil {
- _, found := values[paramName]
- if !found {
- return false, nil
- }
- return true, BindStringToObject(values.Get(paramName), dest)
- }
- if t.Kind() != reflect.Struct {
- return false, fmt.Errorf("unmarshalling query arg '%s' into wrong type", paramName)
- }
- fieldsPresent := false
- for i := 0; i < t.NumField(); i++ {
- fieldT := t.Field(i)
- // Skip unsettable fields, such as internal ones.
- if !v.Field(i).CanSet() {
- continue
- }
- // 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
- }
- }
- // At this point, we look up field name in the parameter list.
- fieldVal, found := values[fieldName]
- if found {
- if len(fieldVal) != 1 {
- return false, fmt.Errorf("field '%s' specified multiple times for param '%s'", fieldName, paramName)
- }
- err := BindStringToObject(fieldVal[0], v.Field(i).Addr().Interface())
- if err != nil {
- return false, fmt.Errorf("could not bind query arg '%s' to request object: %s'", paramName, err)
- }
- fieldsPresent = true
- }
- }
- return fieldsPresent, nil
- }
- // indirect
- func indirect(dest interface{}) (interface{}, reflect.Value, reflect.Type) {
- v := reflect.ValueOf(dest)
- if v.Type().NumMethod() > 0 && v.CanInterface() {
- if u, ok := v.Interface().(Binder); ok {
- return u, reflect.Value{}, nil
- }
- }
- v = reflect.Indirect(v)
- t := v.Type()
- // special handling for custom types which might look like an object. We
- // don't want to use object binding on them, but rather treat them as
- // primitive types. time.Time{} is a unique case since we can't add a Binder
- // to it without changing the underlying generated code.
- if t.ConvertibleTo(reflect.TypeOf(time.Time{})) {
- return dest, reflect.Value{}, nil
- }
- if t.ConvertibleTo(reflect.TypeOf(types.Date{})) {
- return dest, reflect.Value{}, nil
- }
- return nil, v, t
- }
|