Skip to content

Commit b8dc2db

Browse files
author
Jamie Tanna
committed
Add 'runtime/' from commit 'ae472eea1b07a07f7b5819705f586e64f15b9005'
git-subtree-dir: runtime git-subtree-mainline: 2182c08 git-subtree-split: ae472ee
2 parents 2182c08 + ae472ee commit b8dc2db

14 files changed

+3693
-0
lines changed

runtime/bind.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2021 DeepMap, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
package runtime
15+
16+
// Binder is the interface implemented by types that can be bound to a query string or a parameter string
17+
// The input can be assumed to be a valid string. If you define a Bind method you are responsible for all
18+
// data being completely bound to the type.
19+
//
20+
// By convention, to approximate the behavior of Bind functions themselves,
21+
// Binder implements Bind("") as a no-op.
22+
type Binder interface {
23+
Bind(src string) error
24+
}

runtime/bindform.go

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
package runtime
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"fmt"
7+
"mime/multipart"
8+
"net/url"
9+
"reflect"
10+
"strconv"
11+
"strings"
12+
13+
"github.com/deepmap/oapi-codegen/pkg/types"
14+
)
15+
16+
const tagName = "json"
17+
const jsonContentType = "application/json"
18+
19+
type RequestBodyEncoding struct {
20+
ContentType string
21+
Style string
22+
Explode *bool
23+
}
24+
25+
func BindMultipart(ptr interface{}, reader multipart.Reader) error {
26+
const defaultMemory = 32 << 20
27+
form, err := reader.ReadForm(defaultMemory)
28+
if err != nil {
29+
return err
30+
}
31+
return BindForm(ptr, form.Value, form.File, nil)
32+
}
33+
34+
func BindForm(ptr interface{}, form map[string][]string, files map[string][]*multipart.FileHeader, encodings map[string]RequestBodyEncoding) error {
35+
ptrVal := reflect.Indirect(reflect.ValueOf(ptr))
36+
if ptrVal.Kind() != reflect.Struct {
37+
return errors.New("form data body should be a struct")
38+
}
39+
tValue := ptrVal.Type()
40+
41+
for i := 0; i < tValue.NumField(); i++ {
42+
field := ptrVal.Field(i)
43+
tag := tValue.Field(i).Tag.Get(tagName)
44+
if !field.CanInterface() || tag == "-" {
45+
continue
46+
}
47+
tag = strings.Split(tag, ",")[0] // extract the name of the tag
48+
if encoding, ok := encodings[tag]; ok {
49+
// custom encoding
50+
values := form[tag]
51+
if len(values) == 0 {
52+
continue
53+
}
54+
value := values[0]
55+
if encoding.ContentType != "" {
56+
if strings.HasPrefix(encoding.ContentType, jsonContentType) {
57+
if err := json.Unmarshal([]byte(value), ptr); err != nil {
58+
return err
59+
}
60+
}
61+
return errors.New("unsupported encoding, only application/json is supported")
62+
} else {
63+
var explode bool
64+
if encoding.Explode != nil {
65+
explode = *encoding.Explode
66+
}
67+
if err := BindStyledParameterWithLocation(encoding.Style, explode, tag, ParamLocationUndefined, value, field.Addr().Interface()); err != nil {
68+
return err
69+
}
70+
}
71+
} else {
72+
// regular form data
73+
if _, err := bindFormImpl(field, form, files, tag); err != nil {
74+
return err
75+
}
76+
}
77+
}
78+
79+
return nil
80+
}
81+
82+
func MarshalForm(ptr interface{}, encodings map[string]RequestBodyEncoding) (url.Values, error) {
83+
ptrVal := reflect.Indirect(reflect.ValueOf(ptr))
84+
if ptrVal.Kind() != reflect.Struct {
85+
return nil, errors.New("form data body should be a struct")
86+
}
87+
tValue := ptrVal.Type()
88+
result := make(url.Values)
89+
for i := 0; i < tValue.NumField(); i++ {
90+
field := ptrVal.Field(i)
91+
tag := tValue.Field(i).Tag.Get(tagName)
92+
if !field.CanInterface() || tag == "-" {
93+
continue
94+
}
95+
omitEmpty := strings.HasSuffix(tag, ",omitempty")
96+
if omitEmpty && field.IsZero() {
97+
continue
98+
}
99+
tag = strings.Split(tag, ",")[0] // extract the name of the tag
100+
if encoding, ok := encodings[tag]; ok && encoding.ContentType != "" {
101+
if strings.HasPrefix(encoding.ContentType, jsonContentType) {
102+
if data, err := json.Marshal(field); err != nil { //nolint:staticcheck
103+
return nil, err
104+
} else {
105+
result[tag] = append(result[tag], string(data))
106+
}
107+
}
108+
return nil, errors.New("unsupported encoding, only application/json is supported")
109+
} else {
110+
marshalFormImpl(field, result, tag)
111+
}
112+
}
113+
return result, nil
114+
}
115+
116+
func bindFormImpl(v reflect.Value, form map[string][]string, files map[string][]*multipart.FileHeader, name string) (bool, error) {
117+
var hasData bool
118+
switch v.Kind() {
119+
case reflect.Interface:
120+
return bindFormImpl(v.Elem(), form, files, name)
121+
case reflect.Ptr:
122+
ptrData := v.Elem()
123+
if !ptrData.IsValid() {
124+
ptrData = reflect.New(v.Type().Elem())
125+
}
126+
ptrHasData, err := bindFormImpl(ptrData, form, files, name)
127+
if err == nil && ptrHasData && !v.Elem().IsValid() {
128+
v.Set(ptrData)
129+
}
130+
return ptrHasData, err
131+
case reflect.Slice:
132+
if files := append(files[name], files[name+"[]"]...); len(files) != 0 {
133+
if _, ok := v.Interface().([]types.File); ok {
134+
result := make([]types.File, len(files))
135+
for i, file := range files {
136+
result[i].InitFromMultipart(file)
137+
}
138+
v.Set(reflect.ValueOf(result))
139+
hasData = true
140+
}
141+
}
142+
indexedElementsCount := indexedElementsCount(form, files, name)
143+
items := append(form[name], form[name+"[]"]...)
144+
if indexedElementsCount+len(items) != 0 {
145+
result := reflect.MakeSlice(v.Type(), indexedElementsCount+len(items), indexedElementsCount+len(items))
146+
for i := 0; i < indexedElementsCount; i++ {
147+
if _, err := bindFormImpl(result.Index(i), form, files, fmt.Sprintf("%s[%v]", name, i)); err != nil {
148+
return false, err
149+
}
150+
}
151+
for i, item := range items {
152+
if err := BindStringToObject(item, result.Index(indexedElementsCount+i).Addr().Interface()); err != nil {
153+
return false, err
154+
}
155+
}
156+
v.Set(result)
157+
hasData = true
158+
}
159+
case reflect.Struct:
160+
if files := files[name]; len(files) != 0 {
161+
if file, ok := v.Interface().(types.File); ok {
162+
file.InitFromMultipart(files[0])
163+
v.Set(reflect.ValueOf(file))
164+
return true, nil
165+
}
166+
}
167+
for i := 0; i < v.NumField(); i++ {
168+
field := v.Type().Field(i)
169+
tag := field.Tag.Get(tagName)
170+
if field.Name == "AdditionalProperties" && field.Type.Kind() == reflect.Map && tag == "-" {
171+
additionalPropertiesHasData, err := bindAdditionalProperties(v.Field(i), v, form, files, name)
172+
if err != nil {
173+
return false, err
174+
}
175+
hasData = hasData || additionalPropertiesHasData
176+
}
177+
if !v.Field(i).CanInterface() || tag == "-" {
178+
continue
179+
}
180+
tag = strings.Split(tag, ",")[0] // extract the name of the tag
181+
fieldHasData, err := bindFormImpl(v.Field(i), form, files, fmt.Sprintf("%s[%s]", name, tag))
182+
if err != nil {
183+
return false, err
184+
}
185+
hasData = hasData || fieldHasData
186+
}
187+
return hasData, nil
188+
default:
189+
value := form[name]
190+
if len(value) != 0 {
191+
return true, BindStringToObject(value[0], v.Addr().Interface())
192+
}
193+
}
194+
return hasData, nil
195+
}
196+
197+
func indexedElementsCount(form map[string][]string, files map[string][]*multipart.FileHeader, name string) int {
198+
name += "["
199+
maxIndex := -1
200+
for k := range form {
201+
if strings.HasPrefix(k, name) {
202+
str := strings.TrimPrefix(k, name)
203+
str = str[:strings.Index(str, "]")]
204+
if idx, err := strconv.Atoi(str); err == nil {
205+
if idx > maxIndex {
206+
maxIndex = idx
207+
}
208+
}
209+
}
210+
}
211+
for k := range files {
212+
if strings.HasPrefix(k, name) {
213+
str := strings.TrimPrefix(k, name)
214+
str = str[:strings.Index(str, "]")]
215+
if idx, err := strconv.Atoi(str); err == nil {
216+
if idx > maxIndex {
217+
maxIndex = idx
218+
}
219+
}
220+
}
221+
}
222+
return maxIndex + 1
223+
}
224+
225+
func bindAdditionalProperties(additionalProperties reflect.Value, parentStruct reflect.Value, form map[string][]string, files map[string][]*multipart.FileHeader, name string) (bool, error) {
226+
hasData := false
227+
valueType := additionalProperties.Type().Elem()
228+
229+
// store all fixed properties in a set
230+
fieldsSet := make(map[string]struct{})
231+
for i := 0; i < parentStruct.NumField(); i++ {
232+
tag := parentStruct.Type().Field(i).Tag.Get(tagName)
233+
if !parentStruct.Field(i).CanInterface() || tag == "-" {
234+
continue
235+
}
236+
tag = strings.Split(tag, ",")[0]
237+
fieldsSet[tag] = struct{}{}
238+
}
239+
240+
result := reflect.MakeMap(additionalProperties.Type())
241+
for k := range form {
242+
if strings.HasPrefix(k, name+"[") {
243+
key := strings.TrimPrefix(k, name+"[")
244+
key = key[:strings.Index(key, "]")]
245+
if _, ok := fieldsSet[key]; ok {
246+
continue
247+
}
248+
value := reflect.New(valueType)
249+
ptrHasData, err := bindFormImpl(value, form, files, fmt.Sprintf("%s[%s]", name, key))
250+
if err != nil {
251+
return false, err
252+
}
253+
result.SetMapIndex(reflect.ValueOf(key), value.Elem())
254+
hasData = hasData || ptrHasData
255+
}
256+
}
257+
for k := range files {
258+
if strings.HasPrefix(k, name+"[") {
259+
key := strings.TrimPrefix(k, name+"[")
260+
key = key[:strings.Index(key, "]")]
261+
if _, ok := fieldsSet[key]; ok {
262+
continue
263+
}
264+
value := reflect.New(valueType)
265+
result.SetMapIndex(reflect.ValueOf(key), value)
266+
ptrHasData, err := bindFormImpl(value, form, files, fmt.Sprintf("%s[%s]", name, key))
267+
if err != nil {
268+
return false, err
269+
}
270+
result.SetMapIndex(reflect.ValueOf(key), value.Elem())
271+
hasData = hasData || ptrHasData
272+
}
273+
}
274+
if hasData {
275+
additionalProperties.Set(result)
276+
}
277+
return hasData, nil
278+
}
279+
280+
func marshalFormImpl(v reflect.Value, result url.Values, name string) {
281+
switch v.Kind() {
282+
case reflect.Interface, reflect.Ptr:
283+
marshalFormImpl(v.Elem(), result, name)
284+
case reflect.Slice:
285+
for i := 0; i < v.Len(); i++ {
286+
elem := v.Index(i)
287+
marshalFormImpl(elem, result, fmt.Sprintf("%s[%v]", name, i))
288+
}
289+
case reflect.Struct:
290+
for i := 0; i < v.NumField(); i++ {
291+
field := v.Type().Field(i)
292+
tag := field.Tag.Get(tagName)
293+
if field.Name == "AdditionalProperties" && tag == "-" {
294+
iter := v.MapRange()
295+
for iter.Next() {
296+
marshalFormImpl(iter.Value(), result, fmt.Sprintf("%s[%s]", name, iter.Key().String()))
297+
}
298+
continue
299+
}
300+
if !v.Field(i).CanInterface() || tag == "-" {
301+
continue
302+
}
303+
tag = strings.Split(tag, ",")[0] // extract the name of the tag
304+
marshalFormImpl(v.Field(i), result, fmt.Sprintf("%s[%s]", name, tag))
305+
}
306+
default:
307+
result[name] = append(result[name], fmt.Sprint(v.Interface()))
308+
}
309+
}

0 commit comments

Comments
 (0)