worker: use openapi spec and generated code
Write an openapi spec for the worker API and use `deepmap/oapi-codegen`
to generate scaffolding for the server-side using the `labstack/echo`
server.
Incidentally, echo by default returns the errors in the same format that
worker API always has:
{ "message": "..." }
The API itself is unchanged to make this change easier to understand. It
will be changed to better suit our needs in future commits.
This commit is contained in:
parent
396c2cedce
commit
ad11ceecf4
112 changed files with 13721 additions and 389 deletions
447
vendor/github.com/deepmap/oapi-codegen/pkg/runtime/bindparam.go
generated
vendored
Normal file
447
vendor/github.com/deepmap/oapi-codegen/pkg/runtime/bindparam.go
generated
vendored
Normal file
|
|
@ -0,0 +1,447 @@
|
|||
// 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 {
|
||||
// special handling for custom types
|
||||
switch dest.(type) {
|
||||
case *types.Date:
|
||||
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 {
|
||||
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)
|
||||
|
||||
// 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
|
||||
}
|
||||
116
vendor/github.com/deepmap/oapi-codegen/pkg/runtime/bindstring.go
generated
vendored
Normal file
116
vendor/github.com/deepmap/oapi-codegen/pkg/runtime/bindstring.go
generated
vendored
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
// 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/deepmap/oapi-codegen/pkg/types"
|
||||
)
|
||||
|
||||
// This function takes a string, and attempts to assign it to the destination
|
||||
// interface via whatever type conversion is necessary. We have to do this
|
||||
// via reflection instead of a much simpler type switch so that we can handle
|
||||
// type aliases. This function was the easy way out, the better way, since we
|
||||
// know the destination type each place that we use this, is to generate code
|
||||
// to read each specific type.
|
||||
func BindStringToObject(src string, dst interface{}) error {
|
||||
var err error
|
||||
|
||||
v := reflect.ValueOf(dst)
|
||||
t := reflect.TypeOf(dst)
|
||||
|
||||
// We need to dereference pointers
|
||||
if t.Kind() == reflect.Ptr {
|
||||
v = reflect.Indirect(v)
|
||||
t = v.Type()
|
||||
}
|
||||
|
||||
// The resulting type must be settable. reflect will catch issues like
|
||||
// passing the destination by value.
|
||||
if !v.CanSet() {
|
||||
return errors.New("destination is not settable")
|
||||
}
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
var val int64
|
||||
val, err = strconv.ParseInt(src, 10, 64)
|
||||
if err == nil {
|
||||
v.SetInt(val)
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
var val uint64
|
||||
val, err = strconv.ParseUint(src, 10, 64)
|
||||
if err == nil {
|
||||
v.SetUint(val)
|
||||
}
|
||||
case reflect.String:
|
||||
v.SetString(src)
|
||||
err = nil
|
||||
case reflect.Float64, reflect.Float32:
|
||||
var val float64
|
||||
val, err = strconv.ParseFloat(src, 64)
|
||||
if err == nil {
|
||||
v.SetFloat(val)
|
||||
}
|
||||
case reflect.Bool:
|
||||
var val bool
|
||||
val, err = strconv.ParseBool(src)
|
||||
if err == nil {
|
||||
v.SetBool(val)
|
||||
}
|
||||
case reflect.Struct:
|
||||
switch dstType := dst.(type) {
|
||||
case *time.Time:
|
||||
// Don't fail on empty string.
|
||||
if src == "" {
|
||||
return nil
|
||||
}
|
||||
// Time is a special case of a struct that we handle
|
||||
parsedTime, err := time.Parse(time.RFC3339Nano, src)
|
||||
if err != nil {
|
||||
parsedTime, err = time.Parse(types.DateFormat, src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing '%s' as RFC3339 or 2006-01-02 time: %s", src, err)
|
||||
}
|
||||
}
|
||||
*dstType = parsedTime
|
||||
return nil
|
||||
case *types.Date:
|
||||
// Don't fail on empty string.
|
||||
if src == "" {
|
||||
return nil
|
||||
}
|
||||
parsedTime, err := time.Parse(types.DateFormat, src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing '%s' as date: %s", src, err)
|
||||
}
|
||||
dstType.Time = parsedTime
|
||||
return nil
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
// We've got a bunch of types unimplemented, don't fail silently.
|
||||
err = fmt.Errorf("can not bind to destination of type: %s", t.Kind())
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("error binding string parameter: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
332
vendor/github.com/deepmap/oapi-codegen/pkg/runtime/deepobject.go
generated
vendored
Normal file
332
vendor/github.com/deepmap/oapi-codegen/pkg/runtime/deepobject.go
generated
vendored
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
package runtime
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/deepmap/oapi-codegen/pkg/types"
|
||||
)
|
||||
|
||||
func marshalDeepObject(in interface{}, path []string) ([]string, error) {
|
||||
var result []string
|
||||
|
||||
switch t := in.(type) {
|
||||
case []interface{}:
|
||||
// For the array, we will use numerical subscripts of the form [x],
|
||||
// in the same order as the array.
|
||||
for i, iface := range t {
|
||||
newPath := append(path, strconv.Itoa(i))
|
||||
fields, err := marshalDeepObject(iface, newPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error traversing array")
|
||||
}
|
||||
result = append(result, fields...)
|
||||
}
|
||||
case map[string]interface{}:
|
||||
// For a map, each key (field name) becomes a member of the path, and
|
||||
// we recurse. First, sort the keys.
|
||||
keys := make([]string, len(t))
|
||||
i := 0
|
||||
for k := range t {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
// Now, for each key, we recursively marshal it.
|
||||
for _, k := range keys {
|
||||
newPath := append(path, k)
|
||||
fields, err := marshalDeepObject(t[k], newPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error traversing map")
|
||||
}
|
||||
result = append(result, fields...)
|
||||
}
|
||||
default:
|
||||
// Now, for a concrete value, we will turn the path elements
|
||||
// into a deepObject style set of subscripts. [a, b, c] turns into
|
||||
// [a][b][c]
|
||||
prefix := "[" + strings.Join(path, "][") + "]"
|
||||
result = []string{
|
||||
prefix + fmt.Sprintf("=%v", t),
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func MarshalDeepObject(i interface{}, paramName string) (string, error) {
|
||||
// We're going to marshal to JSON and unmarshal into an interface{},
|
||||
// which will use the json pkg to deal with all the field annotations. We
|
||||
// can then walk the generic object structure to produce a deepObject. This
|
||||
// isn't efficient and it would be more efficient to reflect on our own,
|
||||
// but it's complicated, error-prone code.
|
||||
buf, err := json.Marshal(i)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to marshal input to JSON")
|
||||
}
|
||||
var i2 interface{}
|
||||
err = json.Unmarshal(buf, &i2)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to unmarshal JSON")
|
||||
}
|
||||
fields, err := marshalDeepObject(i2, nil)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "error traversing JSON structure")
|
||||
}
|
||||
|
||||
// Prefix the param name to each subscripted field.
|
||||
for i := range fields {
|
||||
fields[i] = paramName + fields[i]
|
||||
}
|
||||
return strings.Join(fields, "&"), nil
|
||||
}
|
||||
|
||||
type fieldOrValue struct {
|
||||
fields map[string]fieldOrValue
|
||||
value string
|
||||
}
|
||||
|
||||
func (f *fieldOrValue) appendPathValue(path []string, value string) {
|
||||
fieldName := path[0]
|
||||
if len(path) == 1 {
|
||||
f.fields[fieldName] = fieldOrValue{value: value}
|
||||
return
|
||||
}
|
||||
|
||||
pv, found := f.fields[fieldName]
|
||||
if !found {
|
||||
pv = fieldOrValue{
|
||||
fields: make(map[string]fieldOrValue),
|
||||
}
|
||||
f.fields[fieldName] = pv
|
||||
}
|
||||
pv.appendPathValue(path[1:], value)
|
||||
}
|
||||
|
||||
func makeFieldOrValue(paths [][]string, values []string) fieldOrValue {
|
||||
|
||||
f := fieldOrValue{
|
||||
fields: make(map[string]fieldOrValue),
|
||||
}
|
||||
for i := range paths {
|
||||
path := paths[i]
|
||||
value := values[i]
|
||||
f.appendPathValue(path, value)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func UnmarshalDeepObject(dst interface{}, paramName string, params url.Values) error {
|
||||
// Params are all the query args, so we need those that look like
|
||||
// "paramName["...
|
||||
var fieldNames []string
|
||||
var fieldValues []string
|
||||
searchStr := paramName + "["
|
||||
for pName, pValues := range params {
|
||||
if strings.HasPrefix(pName, searchStr) {
|
||||
// trim the parameter name from the full name.
|
||||
pName = pName[len(paramName):]
|
||||
fieldNames = append(fieldNames, pName)
|
||||
if len(pValues) != 1 {
|
||||
return fmt.Errorf("%s has multiple values", pName)
|
||||
}
|
||||
fieldValues = append(fieldValues, pValues[0])
|
||||
}
|
||||
}
|
||||
|
||||
// Now, for each field, reconstruct its subscript path and value
|
||||
paths := make([][]string, len(fieldNames))
|
||||
for i, path := range fieldNames {
|
||||
path = strings.TrimLeft(path, "[")
|
||||
path = strings.TrimRight(path, "]")
|
||||
paths[i] = strings.Split(path, "][")
|
||||
}
|
||||
|
||||
fieldPaths := makeFieldOrValue(paths, fieldValues)
|
||||
err := assignPathValues(dst, fieldPaths)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error assigning value to destination")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// This returns a field name, either using the variable name, or the json
|
||||
// annotation if that exists.
|
||||
func getFieldName(f reflect.StructField) string {
|
||||
n := f.Name
|
||||
tag, found := f.Tag.Lookup("json")
|
||||
if found {
|
||||
// If we have a json field, and the first part of it before the
|
||||
// first comma is non-empty, that's our field name.
|
||||
parts := strings.Split(tag, ",")
|
||||
if parts[0] != "" {
|
||||
n = parts[0]
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Create a map of field names that we'll see in the deepObject to reflect
|
||||
// field indices on the given type.
|
||||
func fieldIndicesByJsonTag(i interface{}) (map[string]int, error) {
|
||||
t := reflect.TypeOf(i)
|
||||
if t.Kind() != reflect.Struct {
|
||||
return nil, errors.New("expected a struct as input")
|
||||
}
|
||||
|
||||
n := t.NumField()
|
||||
fieldMap := make(map[string]int)
|
||||
for i := 0; i < n; i++ {
|
||||
field := t.Field(i)
|
||||
fieldName := getFieldName(field)
|
||||
fieldMap[fieldName] = i
|
||||
}
|
||||
return fieldMap, nil
|
||||
}
|
||||
|
||||
func assignPathValues(dst interface{}, pathValues fieldOrValue) error {
|
||||
//t := reflect.TypeOf(dst)
|
||||
v := reflect.ValueOf(dst)
|
||||
|
||||
iv := reflect.Indirect(v)
|
||||
it := iv.Type()
|
||||
|
||||
switch it.Kind() {
|
||||
case reflect.Slice:
|
||||
sliceLength := len(pathValues.fields)
|
||||
dstSlice := reflect.MakeSlice(it, sliceLength, sliceLength)
|
||||
err := assignSlice(dstSlice, pathValues)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error assigning slice")
|
||||
}
|
||||
iv.Set(dstSlice)
|
||||
return nil
|
||||
case reflect.Struct:
|
||||
// Some special types we care about are structs. Handle them
|
||||
// here.
|
||||
if _, isDate := iv.Interface().(types.Date); isDate {
|
||||
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))
|
||||
}
|
||||
if _, isTime := iv.Interface().(time.Time); isTime {
|
||||
var tm time.Time
|
||||
var err error
|
||||
tm, err = time.Parse(types.DateFormat, pathValues.value)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid date format")
|
||||
}
|
||||
iv.Set(reflect.ValueOf(tm))
|
||||
}
|
||||
|
||||
fieldMap, err := fieldIndicesByJsonTag(iv.Interface())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed enumerating fields")
|
||||
}
|
||||
for _, fieldName := range sortedFieldOrValueKeys(pathValues.fields) {
|
||||
fieldValue := pathValues.fields[fieldName]
|
||||
fieldIndex, found := fieldMap[fieldName]
|
||||
if !found {
|
||||
return fmt.Errorf("field [%s] is not present in destination object", fieldName)
|
||||
}
|
||||
field := iv.Field(fieldIndex)
|
||||
err = assignPathValues(field.Addr().Interface(), fieldValue)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error assigning field [%s]", fieldName)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case reflect.Ptr:
|
||||
// If we have a pointer after redirecting, it means we're dealing with
|
||||
// an optional field, such as *string, which was passed in as &foo. We
|
||||
// will allocate it if necessary, and call ourselves with a different
|
||||
// interface.
|
||||
dstVal := reflect.New(it.Elem())
|
||||
dstPtr := dstVal.Interface()
|
||||
err := assignPathValues(dstPtr, pathValues)
|
||||
iv.Set(dstVal)
|
||||
return err
|
||||
case reflect.Bool:
|
||||
val, err := strconv.ParseBool(pathValues.value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("expected a valid bool, got %s", pathValues.value)
|
||||
}
|
||||
iv.SetBool(val)
|
||||
return nil
|
||||
case reflect.Float32:
|
||||
val, err := strconv.ParseFloat(pathValues.value, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("expected a valid float, got %s", pathValues.value)
|
||||
}
|
||||
iv.SetFloat(val)
|
||||
return nil
|
||||
case reflect.Float64:
|
||||
val, err := strconv.ParseFloat(pathValues.value, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("expected a valid float, got %s", pathValues.value)
|
||||
}
|
||||
iv.SetFloat(val)
|
||||
return nil
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
val, err := strconv.ParseInt(pathValues.value, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("expected a valid int, got %s", pathValues.value)
|
||||
}
|
||||
iv.SetInt(val)
|
||||
return nil
|
||||
case reflect.String:
|
||||
iv.SetString(pathValues.value)
|
||||
return nil
|
||||
default:
|
||||
return errors.New("unhandled type: " + it.String())
|
||||
}
|
||||
}
|
||||
|
||||
func assignSlice(dst reflect.Value, pathValues fieldOrValue) error {
|
||||
// Gather up the values
|
||||
nValues := len(pathValues.fields)
|
||||
values := make([]string, nValues)
|
||||
// We expect to have consecutive array indices in the map
|
||||
for i := 0; i < nValues; i++ {
|
||||
indexStr := strconv.Itoa(i)
|
||||
fv, found := pathValues.fields[indexStr]
|
||||
if !found {
|
||||
return errors.New("array deepObjects must have consecutive indices")
|
||||
}
|
||||
values[i] = fv.value
|
||||
}
|
||||
|
||||
// This could be cleaner, but we can call into assignPathValues to
|
||||
// avoid recreating this logic.
|
||||
for i:=0; i < nValues; i++ {
|
||||
dstElem := dst.Index(i).Addr()
|
||||
err := assignPathValues(dstElem.Interface(), fieldOrValue{value:values[i]})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error binding array")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func sortedFieldOrValueKeys(m map[string]fieldOrValue) []string {
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
337
vendor/github.com/deepmap/oapi-codegen/pkg/runtime/styleparam.go
generated
vendored
Normal file
337
vendor/github.com/deepmap/oapi-codegen/pkg/runtime/styleparam.go
generated
vendored
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
// 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Given an input value, such as a primitive type, array or object, turn it
|
||||
// into a parameter based on style/explode definition.
|
||||
func StyleParam(style string, explode bool, paramName string, 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()
|
||||
}
|
||||
|
||||
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, sliceVal)
|
||||
case reflect.Struct:
|
||||
return styleStruct(style, explode, paramName, value)
|
||||
case reflect.Map:
|
||||
return styleMap(style, explode, paramName, value)
|
||||
default:
|
||||
return stylePrimitive(style, explode, paramName, value)
|
||||
}
|
||||
}
|
||||
|
||||
func styleSlice(style string, explode bool, paramName string, 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
|
||||
parts := make([]string, len(values))
|
||||
for i, v := range values {
|
||||
parts[i], err = primitiveToString(v)
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
// 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 {
|
||||
return timeVal.Format(time.RFC3339Nano), true
|
||||
}
|
||||
|
||||
if timeVal, ok := value.(time.Time); ok {
|
||||
return timeVal.Format(time.RFC3339Nano), 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)
|
||||
}
|
||||
|
||||
if style == "deepObject" {
|
||||
if !explode {
|
||||
return "", errors.New("deepObjects must be exploded")
|
||||
}
|
||||
return MarshalDeepObject(value, paramName)
|
||||
}
|
||||
|
||||
// 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, fieldDict)
|
||||
}
|
||||
|
||||
func styleMap(style string, explode bool, paramName string, 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, fieldDict)
|
||||
}
|
||||
|
||||
func processFieldDict(style string, explode bool, paramName string, 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 := fieldDict[k]
|
||||
parts = append(parts, k+"="+v)
|
||||
}
|
||||
} else {
|
||||
for _, k := range sortedKeys(fieldDict) {
|
||||
v := fieldDict[k]
|
||||
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, 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 + strVal, 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
|
||||
|
||||
// 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.Int32, reflect.Int64, reflect.Int:
|
||||
output = strconv.FormatInt(v.Int(), 10)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
output = strconv.FormatFloat(v.Float(), 'f', -1, 64)
|
||||
case reflect.Bool:
|
||||
if v.Bool() {
|
||||
output = "true"
|
||||
} else {
|
||||
output = "false"
|
||||
}
|
||||
case reflect.String:
|
||||
output = v.String()
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported type %s", reflect.TypeOf(value).String())
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
30
vendor/github.com/deepmap/oapi-codegen/pkg/types/date.go
generated
vendored
Normal file
30
vendor/github.com/deepmap/oapi-codegen/pkg/types/date.go
generated
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
const DateFormat = "2006-01-02"
|
||||
|
||||
type Date struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func (d Date) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(d.Time.Format(DateFormat))
|
||||
}
|
||||
|
||||
func (d *Date) UnmarshalJSON(data []byte) error {
|
||||
var dateStr string
|
||||
err := json.Unmarshal(data, &dateStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parsed, err := time.Parse(DateFormat, dateStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.Time = parsed
|
||||
return nil
|
||||
}
|
||||
27
vendor/github.com/deepmap/oapi-codegen/pkg/types/email.go
generated
vendored
Normal file
27
vendor/github.com/deepmap/oapi-codegen/pkg/types/email.go
generated
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type Email string
|
||||
|
||||
func (e Email) MarshalJSON() ([]byte, error) {
|
||||
if !emailRegex.MatchString(string(e)) {
|
||||
return nil, errors.New("email: failed to pass regex validation")
|
||||
}
|
||||
return json.Marshal(string(e))
|
||||
}
|
||||
|
||||
func (e *Email) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
if !emailRegex.MatchString(s) {
|
||||
return errors.New("email: failed to pass regex validation")
|
||||
}
|
||||
*e = Email(s)
|
||||
return nil
|
||||
}
|
||||
11
vendor/github.com/deepmap/oapi-codegen/pkg/types/regexes.go
generated
vendored
Normal file
11
vendor/github.com/deepmap/oapi-codegen/pkg/types/regexes.go
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package types
|
||||
|
||||
import "regexp"
|
||||
|
||||
const (
|
||||
emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22))))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
|
||||
)
|
||||
|
||||
var (
|
||||
emailRegex = regexp.MustCompile(emailRegexString)
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue