This change will vendor the new version of the exoscale-import post-processor component, but remove all of its code from Packer. After the v1.8.0 release this change should be removed entirely. This vendor process is being used as a workaround for decoupling the exoscale-import component without causing a breaking change in Packer. Users of Exoscale are encouraged to leverage `packer init` for installing the latest version of packer-plugin-exoscale.
464 lines
15 KiB
Go
464 lines
15 KiB
Go
// 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/json"
|
|
"fmt"
|
|
"net/url"
|
|
"reflect"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/deepmap/oapi-codegen/pkg/types"
|
|
)
|
|
|
|
// This function binds a parameter as described in the Path Parameters
|
|
// section here to a Go object:
|
|
// https://swagger.io/docs/specification/serialization/
|
|
func BindStyledParameter(style string, explode bool, paramName string,
|
|
value string, dest interface{}) error {
|
|
|
|
if value == "" {
|
|
return fmt.Errorf("parameter '%s' is empty, can't bind its value", paramName)
|
|
}
|
|
|
|
// 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 unmarshaling
|
|
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 unmarshaling
|
|
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
|
|
}
|
|
|
|
// This 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 {
|
|
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.
|
|
err = bindParamsToExplodedObject(paramName, queryParams, output)
|
|
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)
|
|
}
|
|
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)
|
|
|
|
}
|
|
}
|
|
|
|
// This function 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.
|
|
func bindParamsToExplodedObject(paramName string, values url.Values, dest interface{}) error {
|
|
// Dereference pointers to their destination values
|
|
binder, v, t := indirect(dest)
|
|
if binder != nil {
|
|
return BindStringToObject(values.Get(paramName), dest)
|
|
}
|
|
if t.Kind() != reflect.Struct {
|
|
return fmt.Errorf("unmarshaling query arg '%s' into wrong type", paramName)
|
|
}
|
|
|
|
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 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 fmt.Errorf("could not bind query arg '%s' to request object: %s'", paramName, err)
|
|
}
|
|
}
|
|
}
|
|
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
|
|
}
|