mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 09:22:44 +00:00
Utility to convert versioned runtime objects to a set query parameters
JSON struct tags are used as parameter names, fields that do not have the omitempty marker are always included.
This commit is contained in:
parent
6ab51f3bc0
commit
619332d58e
118
pkg/conversion/queryparams/convert.go
Normal file
118
pkg/conversion/queryparams/convert.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
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 queryparams
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func jsonTag(field reflect.StructField) (string, bool) {
|
||||||
|
structTag := field.Tag.Get("json")
|
||||||
|
if len(structTag) == 0 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
parts := strings.Split(structTag, ",")
|
||||||
|
tag := parts[0]
|
||||||
|
if tag == "-" {
|
||||||
|
tag = ""
|
||||||
|
}
|
||||||
|
omitempty := false
|
||||||
|
parts = parts[1:]
|
||||||
|
for _, part := range parts {
|
||||||
|
if part == "omitempty" {
|
||||||
|
omitempty = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tag, omitempty
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatValue(value interface{}) string {
|
||||||
|
return fmt.Sprintf("%v", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValueKind(kind reflect.Kind) bool {
|
||||||
|
switch kind {
|
||||||
|
case reflect.String, reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16,
|
||||||
|
reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8,
|
||||||
|
reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32,
|
||||||
|
reflect.Float64, reflect.Complex64, reflect.Complex128:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func zeroValue(value reflect.Value) bool {
|
||||||
|
return reflect.DeepEqual(reflect.Zero(value.Type()).Interface(), value.Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
func addParam(values url.Values, tag string, omitempty bool, value reflect.Value) {
|
||||||
|
if omitempty && zeroValue(value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
values.Add(tag, fmt.Sprintf("%v", value.Interface()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func addListOfParams(values url.Values, tag string, omitempty bool, list reflect.Value) {
|
||||||
|
for i := 0; i < list.Len(); i++ {
|
||||||
|
addParam(values, tag, omitempty, list.Index(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert takes a versioned runtime.Object and serializes it to a url.Values object
|
||||||
|
// using JSON tags as parameter names. Only top-level simple values, arrays, and slices
|
||||||
|
// are serialized. Embedded structs, maps, etc. will not be serialized.
|
||||||
|
func Convert(obj runtime.Object) (url.Values, error) {
|
||||||
|
result := url.Values{}
|
||||||
|
if obj == nil {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
var sv reflect.Value
|
||||||
|
switch reflect.TypeOf(obj).Kind() {
|
||||||
|
case reflect.Ptr, reflect.Interface:
|
||||||
|
sv = reflect.ValueOf(obj).Elem()
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Expecting a pointer or interface")
|
||||||
|
}
|
||||||
|
st := sv.Type()
|
||||||
|
if st.Kind() != reflect.Struct {
|
||||||
|
return nil, fmt.Errorf("Expecting a pointer to a struct")
|
||||||
|
}
|
||||||
|
for i := 0; i < st.NumField(); i++ {
|
||||||
|
field := sv.Field(i)
|
||||||
|
tag, omitempty := jsonTag(st.Field(i))
|
||||||
|
if len(tag) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ft := field.Type()
|
||||||
|
switch {
|
||||||
|
case isValueKind(ft.Kind()):
|
||||||
|
addParam(result, tag, omitempty, field)
|
||||||
|
case ft.Kind() == reflect.Array || ft.Kind() == reflect.Slice:
|
||||||
|
if isValueKind(ft.Elem().Kind()) {
|
||||||
|
addListOfParams(result, tag, omitempty, field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
143
pkg/conversion/queryparams/convert_test.go
Normal file
143
pkg/conversion/queryparams/convert_test.go
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
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 queryparams
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type namedString string
|
||||||
|
type namedBool bool
|
||||||
|
|
||||||
|
type bar struct {
|
||||||
|
Float1 float32 `json:"float1"`
|
||||||
|
Float2 float64 `json:"float2"`
|
||||||
|
Int1 int64 `json:"int1,omitempty"`
|
||||||
|
Int2 int32 `json:"int2,omitempty"`
|
||||||
|
Int3 int16 `json:"int3,omitempty"`
|
||||||
|
Str1 string `json:"str1,omitempty"`
|
||||||
|
Ignored int
|
||||||
|
Ignored2 string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*bar) IsAnAPIObject() {}
|
||||||
|
|
||||||
|
type foo struct {
|
||||||
|
Str string `json:"str"`
|
||||||
|
Integer int `json:"integer,omitempty"`
|
||||||
|
Slice []string `json:"slice,omitempty"`
|
||||||
|
Boolean bool `json:"boolean,omitempty"`
|
||||||
|
NamedStr namedString `json:"namedStr,omitempty"`
|
||||||
|
NamedBool namedBool `json:"namedBool,omitempty"`
|
||||||
|
Foobar bar `json:"foobar,omitempty"`
|
||||||
|
Testmap map[string]string `json:"testmap,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*foo) IsAnAPIObject() {}
|
||||||
|
|
||||||
|
func validateResult(t *testing.T, input interface{}, actual, expected url.Values) {
|
||||||
|
local := url.Values{}
|
||||||
|
for k, v := range expected {
|
||||||
|
local[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range actual {
|
||||||
|
if ev, ok := local[k]; !ok || !reflect.DeepEqual(ev, v) {
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%#v: actual value key %s not found in expected map", input, k)
|
||||||
|
} else {
|
||||||
|
t.Errorf("%#v: values don't match: actual: %#v, expected: %#v", input, v, ev)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
delete(local, k)
|
||||||
|
}
|
||||||
|
if len(local) > 0 {
|
||||||
|
t.Errorf("%#v: expected map has keys that were not found in actual map: %#v", input, local)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvert(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input runtime.Object
|
||||||
|
expected url.Values
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: &foo{
|
||||||
|
Str: "hello",
|
||||||
|
},
|
||||||
|
expected: url.Values{"str": {"hello"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: &foo{
|
||||||
|
Str: "test string",
|
||||||
|
Slice: []string{"one", "two", "three"},
|
||||||
|
Integer: 234,
|
||||||
|
Boolean: true,
|
||||||
|
},
|
||||||
|
expected: url.Values{"str": {"test string"}, "slice": {"one", "two", "three"}, "integer": {"234"}, "boolean": {"true"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: &foo{
|
||||||
|
Str: "named types",
|
||||||
|
NamedStr: "value1",
|
||||||
|
NamedBool: true,
|
||||||
|
},
|
||||||
|
expected: url.Values{"str": {"named types"}, "namedStr": {"value1"}, "namedBool": {"true"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: &foo{
|
||||||
|
Str: "ignore embedded struct",
|
||||||
|
Foobar: bar{
|
||||||
|
Float1: 5.0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: url.Values{"str": {"ignore embedded struct"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Ignore untagged fields
|
||||||
|
input: &bar{
|
||||||
|
Float1: 23.5,
|
||||||
|
Float2: 100.7,
|
||||||
|
Int1: 1,
|
||||||
|
Int2: 2,
|
||||||
|
Int3: 3,
|
||||||
|
Ignored: 1,
|
||||||
|
Ignored2: "ignored",
|
||||||
|
},
|
||||||
|
expected: url.Values{"float1": {"23.5"}, "float2": {"100.7"}, "int1": {"1"}, "int2": {"2"}, "int3": {"3"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// include fields that are not tagged omitempty
|
||||||
|
input: &foo{
|
||||||
|
NamedStr: "named str",
|
||||||
|
},
|
||||||
|
expected: url.Values{"str": {""}, "namedStr": {"named str"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
result, err := Convert(test.input)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error while converting %#v: %v", test.input, err)
|
||||||
|
}
|
||||||
|
validateResult(t, test.input, result, test.expected)
|
||||||
|
}
|
||||||
|
}
|
19
pkg/conversion/queryparams/doc.go
Normal file
19
pkg/conversion/queryparams/doc.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
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 queryparams provides conversion from versioned
|
||||||
|
// runtime objects to URL query values
|
||||||
|
package queryparams
|
Loading…
Reference in New Issue
Block a user