go.mod: Update oapi-codegen and kin-openapi

This commit is contained in:
sanne 2022-01-11 19:00:14 +01:00 committed by Sanne Raymaekers
parent add17bba45
commit a83cf95d5b
156 changed files with 29663 additions and 2248 deletions

View file

@ -0,0 +1,24 @@
// Copyright 2021 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
// Binder is the interface implemented by types that can be bound to a query string or a parameter string
// The input can be assumed to be a valid string. If you define a Bind method you are responsible for all
// data being completely bound to the type.
//
// By convention, to approximate the behavior of Bind functions themselves,
// Binder implements Bind("") as a no-op.
type Binder interface {
Bind(src string) error
}

View file

@ -14,6 +14,7 @@
package runtime
import (
"encoding"
"encoding/json"
"fmt"
"net/url"
@ -29,13 +30,51 @@ import (
// This function 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)
}
// This function 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 unmarshaling '%s' text as %T: %s", value, dest, err)
}
return nil
}
// Everything comes in by pointer, dereference it
v := reflect.Indirect(reflect.ValueOf(dest))
@ -395,22 +434,15 @@ func BindQueryParameter(style string, explode bool, required bool, paramName str
// We don't try to be smart here, if the field exists as a query argument,
// set its value.
func bindParamsToExplodedObject(paramName string, values url.Values, dest interface{}) error {
// special handling for custom types
switch dest.(type) {
case *types.Date:
// Dereference pointers to their destination values
binder, v, t := indirect(dest)
if binder != nil {
return BindStringToObject(values.Get(paramName), dest)
case *time.Time:
return BindStringToObject(values.Get(paramName), dest)
}
v := reflect.Indirect(reflect.ValueOf(dest))
if v.Type().Kind() != reflect.Struct {
if t.Kind() != reflect.Struct {
return fmt.Errorf("unmarshaling query arg '%s' into wrong type", paramName)
}
t := v.Type()
for i := 0; i < t.NumField(); i++ {
fieldT := t.Field(i)
@ -445,3 +477,26 @@ func bindParamsToExplodedObject(paramName string, values url.Values, dest interf
}
return 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
}

View file

@ -76,8 +76,12 @@ func BindStringToObject(src string, dst interface{}) error {
v.SetBool(val)
}
case reflect.Struct:
switch dstType := dst.(type) {
case *time.Time:
// if this is not of type Time or of type Date look to see if this is of type Binder.
if dstType, ok := dst.(Binder); ok {
return dstType.Bind(src)
}
if t.ConvertibleTo(reflect.TypeOf(time.Time{})) {
// Don't fail on empty string.
if src == "" {
return nil
@ -90,9 +94,20 @@ func BindStringToObject(src string, dst interface{}) error {
return fmt.Errorf("error parsing '%s' as RFC3339 or 2006-01-02 time: %s", src, err)
}
}
*dstType = parsedTime
// So, assigning this gets a little fun. We have a value to the
// dereference destination. We can't do a conversion to
// time.Time because the result isn't assignable, so we need to
// convert pointers.
if t != reflect.TypeOf(time.Time{}) {
vPtr := v.Addr()
vtPtr := vPtr.Convert(reflect.TypeOf(&time.Time{}))
v = reflect.Indirect(vtPtr)
}
v.Set(reflect.ValueOf(parsedTime))
return nil
case *types.Date:
}
if t.ConvertibleTo(reflect.TypeOf(types.Date{})) {
// Don't fail on empty string.
if src == "" {
return nil
@ -101,9 +116,21 @@ func BindStringToObject(src string, dst interface{}) error {
if err != nil {
return fmt.Errorf("error parsing '%s' as date: %s", src, err)
}
dstType.Time = parsedTime
parsedDate := types.Date{Time: parsedTime}
// We have to do the same dance here to assign, just like with times
// above.
if t != reflect.TypeOf(types.Date{}) {
vPtr := v.Addr()
vtPtr := vPtr.Convert(reflect.TypeOf(&types.Date{}))
v = reflect.Indirect(vtPtr)
}
v.Set(reflect.ValueOf(parsedDate))
return nil
}
// We fall through to the error case below if we haven't handled the
// destination type above.
fallthrough
default:
// We've got a bunch of types unimplemented, don't fail silently.

View file

@ -3,6 +3,7 @@ package runtime
import (
"encoding/json"
"fmt"
"github.com/deepmap/oapi-codegen/pkg/types"
"net/url"
"reflect"
"sort"
@ -11,8 +12,6 @@ import (
"time"
"github.com/pkg/errors"
"github.com/deepmap/oapi-codegen/pkg/types"
)
func marshalDeepObject(in interface{}, path []string) ([]string, error) {
@ -212,26 +211,52 @@ func assignPathValues(dst interface{}, pathValues fieldOrValue) error {
return nil
case reflect.Struct:
// Some special types we care about are structs. Handle them
// here.
if _, isDate := iv.Interface().(types.Date); isDate {
// here. They may be redefined, so we need to do some hoop
// jumping. If the types are aliased, we need to type convert
// the pointer, then set the value of the dereference pointer.
// We check to see if the object implements the Binder interface first.
if dst, isBinder := v.Interface().(Binder); isBinder {
return dst.Bind(pathValues.value)
}
// Then check the legacy types
if it.ConvertibleTo(reflect.TypeOf(types.Date{})) {
var date types.Date
var err error
date.Time, err = time.Parse(types.DateFormat, pathValues.value)
if err != nil {
return errors.Wrap(err, "invalid date format")
}
iv.Set(reflect.ValueOf(date))
dst := iv
if it != reflect.TypeOf(types.Date{}) {
// Types are aliased, convert the pointers.
ivPtr := iv.Addr()
aPtr := ivPtr.Convert(reflect.TypeOf(&types.Date{}))
dst = reflect.Indirect(aPtr)
}
dst.Set(reflect.ValueOf(date))
}
if _, isTime := iv.Interface().(time.Time); isTime {
if it.ConvertibleTo(reflect.TypeOf(time.Time{})) {
var tm time.Time
var err error
tm, err = time.Parse(types.DateFormat, pathValues.value)
tm, err = time.Parse(time.RFC3339Nano, pathValues.value)
if err != nil {
// Fall back to parsing it as a date.
tm, err = time.Parse(types.DateFormat, pathValues.value)
if err != nil {
return fmt.Errorf("error parsing tim as RFC3339 or 2006-01-02 time: %s", err)
}
return errors.Wrap(err, "invalid date format")
}
iv.Set(reflect.ValueOf(tm))
dst := iv
if it != reflect.TypeOf(time.Time{}) {
// Types are aliased, convert the pointers.
ivPtr := iv.Addr()
aPtr := ivPtr.Convert(reflect.TypeOf(&time.Time{}))
dst = reflect.Indirect(aPtr)
}
dst.Set(reflect.ValueOf(tm))
}
fieldMap, err := fieldIndicesByJsonTag(iv.Interface())
if err != nil {
return errors.Wrap(err, "failed enumerating fields")
@ -311,9 +336,9 @@ func assignSlice(dst reflect.Value, pathValues fieldOrValue) error {
// This could be cleaner, but we can call into assignPathValues to
// avoid recreating this logic.
for i:=0; i < nValues; i++ {
for i := 0; i < nValues; i++ {
dstElem := dst.Index(i).Addr()
err := assignPathValues(dstElem.Interface(), fieldOrValue{value:values[i]})
err := assignPathValues(dstElem.Interface(), fieldOrValue{value: values[i]})
if err != nil {
return errors.Wrap(err, "error binding array")
}

View file

@ -14,18 +14,42 @@
package runtime
import (
"errors"
"fmt"
"net/url"
"reflect"
"sort"
"strconv"
"strings"
"time"
"github.com/pkg/errors"
"github.com/deepmap/oapi-codegen/pkg/types"
)
// Given an input value, such as a primitive type, array or object, turn it
// into a parameter based on style/explode definition.
// Parameter escaping works differently based on where a header is found
type ParamLocation int
const (
ParamLocationUndefined ParamLocation = iota
ParamLocationQuery
ParamLocationPath
ParamLocationHeader
ParamLocationCookie
)
// This function 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)
@ -46,17 +70,17 @@ func StyleParam(style string, explode bool, paramName string, value interface{})
for i := 0; i < n; i++ {
sliceVal[i] = v.Index(i).Interface()
}
return styleSlice(style, explode, paramName, sliceVal)
return styleSlice(style, explode, paramName, paramLocation, sliceVal)
case reflect.Struct:
return styleStruct(style, explode, paramName, value)
return styleStruct(style, explode, paramName, paramLocation, value)
case reflect.Map:
return styleMap(style, explode, paramName, value)
return styleMap(style, explode, paramName, paramLocation, value)
default:
return stylePrimitive(style, explode, paramName, value)
return stylePrimitive(style, explode, paramName, paramLocation, value)
}
}
func styleSlice(style string, explode bool, paramName string, values []interface{}) (string, error) {
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")
@ -111,9 +135,12 @@ func styleSlice(style string, explode bool, paramName string, values []interface
// 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 {
parts[i], err = primitiveToString(v)
part, err = primitiveToString(v)
part = escapeParameterString(part, paramLocation)
parts[i] = part
if err != nil {
return "", fmt.Errorf("error formatting '%s': %s", paramName, err)
}
@ -132,24 +159,35 @@ func sortedKeys(strMap map[string]string) []string {
return keys
}
// This is a special case. The struct may be a date or time, in
// which case, marshal it in correct format.
func marshalDateTimeValue(value interface{}) (string, bool) {
v := reflect.Indirect(reflect.ValueOf(value))
t := v.Type()
// This is a special case. The struct may be a time, in which case, marshal
// it in RFC3339 format.
func marshalTimeValue(value interface{}) (string, bool) {
if timeVal, ok := value.(*time.Time); ok {
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 timeVal, ok := value.(time.Time); ok {
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
}
return "", false
}
func styleStruct(style string, explode bool, paramName string, value interface{}) (string, error) {
if timeVal, ok := marshalTimeValue(value); ok {
return stylePrimitive(style, explode, paramName, timeVal)
func styleStruct(style string, explode bool, paramName string, paramLocation ParamLocation, value interface{}) (string, error) {
if timeVal, ok := marshalDateTimeValue(value); ok {
styledVal, err := stylePrimitive(style, explode, paramName, paramLocation, timeVal)
if err != nil {
return "", errors.Wrap(err, "failed to style time")
}
return styledVal, nil
}
if style == "deepObject" {
@ -191,10 +229,10 @@ func styleStruct(style string, explode bool, paramName string, value interface{}
fieldDict[fieldName] = str
}
return processFieldDict(style, explode, paramName, fieldDict)
return processFieldDict(style, explode, paramName, paramLocation, fieldDict)
}
func styleMap(style string, explode bool, paramName string, value interface{}) (string, error) {
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")
@ -215,11 +253,10 @@ func styleMap(style string, explode bool, paramName string, value interface{}) (
}
fieldDict[fieldName] = str
}
return processFieldDict(style, explode, paramName, fieldDict)
return processFieldDict(style, explode, paramName, paramLocation, fieldDict)
}
func processFieldDict(style string, explode bool, paramName string, fieldDict map[string]string) (string, error) {
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
@ -227,12 +264,12 @@ func processFieldDict(style string, explode bool, paramName string, fieldDict ma
if style != "deepObject" {
if explode {
for _, k := range sortedKeys(fieldDict) {
v := fieldDict[k]
v := escapeParameterString(fieldDict[k], paramLocation)
parts = append(parts, k+"="+v)
}
} else {
for _, k := range sortedKeys(fieldDict) {
v := fieldDict[k]
v := escapeParameterString(fieldDict[k], paramLocation)
parts = append(parts, k)
parts = append(parts, v)
}
@ -286,7 +323,7 @@ func processFieldDict(style string, explode bool, paramName string, fieldDict ma
return prefix + strings.Join(parts, separator), nil
}
func stylePrimitive(style string, explode bool, paramName string, value interface{}) (string, error) {
func stylePrimitive(style string, explode bool, paramName string, paramLocation ParamLocation, value interface{}) (string, error) {
strVal, err := primitiveToString(value)
if err != nil {
return "", err
@ -304,7 +341,7 @@ func stylePrimitive(style string, explode bool, paramName string, value interfac
default:
return "", fmt.Errorf("unsupported style '%s'", style)
}
return prefix + strVal, nil
return prefix + escapeParameterString(strVal, paramLocation), nil
}
// Converts a primitive value to a string. We need to do this based on the
@ -320,8 +357,10 @@ func primitiveToString(value interface{}) (string, error) {
switch kind {
case reflect.Int8, reflect.Int32, reflect.Int64, reflect.Int:
output = strconv.FormatInt(v.Int(), 10)
case reflect.Float32, reflect.Float64:
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"
@ -335,3 +374,17 @@ func primitiveToString(value interface{}) (string, error) {
}
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
}
}