func addObjectParams(ws *restful.WebService, route *restful.RouteBuilder, obj interface{}) error {
	sv, err := conversion.EnforcePtr(obj)
	if err != nil {
		return err
	}
	st := sv.Type()
	switch st.Kind() {
	case reflect.Struct:
		for i := 0; i < st.NumField(); i++ {
			name := st.Field(i).Name
			sf, ok := st.FieldByName(name)
			if !ok {
				continue
			}
			switch sf.Type.Kind() {
			case reflect.Interface, reflect.Struct:
			case reflect.Ptr:
				if (sf.Type.Elem().Kind() == reflect.Interface || sf.Type.Elem().Kind() == reflect.Struct) && strings.TrimPrefix(sf.Type.String(), "*") != "metav1.Time" {
					continue
				}
				fallthrough
			default:
				jsonTag := sf.Tag.Get("json")
				if len(jsonTag) == 0 {
					continue
				}
				jsonName := strings.SplitN(jsonTag, ",", 2)[0]
				if len(jsonName) == 0 {
					continue
				}
				var desc string
				if docable, ok := obj.(documentable); ok {
					desc = docable.SwaggerDoc()[jsonName]
				}
				if route.ParameterNamed(jsonName) == nil {
					route.Param(ws.QueryParameter(jsonName, desc).DataType(typeToJSON(sf.Type.String())))
				}
			}
		}
	}
	return nil
}
addObjectParams converts a runtime.Object into a set of go-restful Param() definitions on the route.
The object must be a pointer to a struct; only fields at the top level of the struct that are not
themselves interfaces or structs are used; only fields with a json tag that is non empty (the standard
Go JSON behavior for omitting a field) become query parameters. The name of the query parameter is
the JSON field name. If a description struct tag is set on the field, that description is used on the
query parameter. In essence, it converts a standard JSON top level object into a query param schema.
package installer
import (
	"fmt"
	gpath "path"
	"reflect"
	"strings"
	"time"
	"k8s.io/apimachinery/pkg/conversion"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	utilerrors "k8s.io/apimachinery/pkg/util/errors"
	"k8s.io/apiserver/pkg/endpoints"
	"k8s.io/apiserver/pkg/endpoints/discovery"
	"k8s.io/apiserver/pkg/endpoints/handlers"
	"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
	"k8s.io/apiserver/pkg/endpoints/request"
	"k8s.io/apiserver/pkg/registry/rest"
	"github.com/emicklei/go-restful"
	cm_handlers "sigs.k8s.io/custom-metrics-apiserver/pkg/apiserver/endpoints/handlers"
	cm_rest "sigs.k8s.io/custom-metrics-apiserver/pkg/apiserver/registry/rest"
)
type MetricsAPIGroupVersion struct {
	DynamicStorage rest.Storage
	*endpoints.APIGroupVersion
	ResourceLister discovery.APIResourceLister
	Handlers apiHandlers
}
type apiHandlers interface {
	registerResourceHandlers(a *MetricsAPIInstaller, ws *restful.WebService) error
}
func (g *MetricsAPIGroupVersion) InstallREST(container *restful.Container) error {
	installer := g.newDynamicInstaller()
	ws := installer.NewWebService()
	registrationErrors := installer.Install(ws)
	lister := g.ResourceLister
	if lister == nil {
		return fmt.Errorf("must provide a dynamic lister for dynamic API groups")
	}
	versionDiscoveryHandler := discovery.NewAPIVersionHandler(g.Serializer, g.GroupVersion, lister)
	versionDiscoveryHandler.AddToWebService(ws)
	container.Add(ws)
	return utilerrors.NewAggregate(registrationErrors)
}
func (g *MetricsAPIGroupVersion) newDynamicInstaller() *MetricsAPIInstaller {
	prefix := gpath.Join(g.Root, g.GroupVersion.Group, g.GroupVersion.Version)
	installer := &MetricsAPIInstaller{
		group:             g,
		prefix:            prefix,
		minRequestTimeout: g.MinRequestTimeout,
		handlers:          g.Handlers,
	}
	return installer
}
type MetricsAPIInstaller struct {
	group             *MetricsAPIGroupVersion
	prefix            string  
	minRequestTimeout time.Duration
	handlers          apiHandlers
}
func (a *MetricsAPIInstaller) Install(ws *restful.WebService) (errors []error) {
	errors = make([]error, 0)
	err := a.handlers.registerResourceHandlers(a, ws)
	if err != nil {
		errors = append(errors, fmt.Errorf("error in registering custom metrics resource: %v", err))
	}
	return errors
}
func (a *MetricsAPIInstaller) NewWebService() *restful.WebService {
	ws := new(restful.WebService)
	ws.Path(a.prefix)
	ws.Doc("API at " + a.prefix)
	ws.Consumes("*/*")
	mediaTypes, streamMediaTypes := negotiation.MediaTypesForSerializer(a.group.Serializer)
	ws.Produces(append(mediaTypes, streamMediaTypes...)...)
	ws.ApiVersion(a.group.GroupVersion.String())
	return ws
}
func indirectArbitraryPointer(ptrToObject interface{}) interface{} {
	return reflect.Indirect(reflect.ValueOf(ptrToObject)).Interface()
}
func (a *MetricsAPIInstaller) getResourceKind(storage rest.Storage) (schema.GroupVersionKind, error) {
	object := storage.New()
	fqKinds, _, err := a.group.Typer.ObjectKinds(object)
	if err != nil {
		return schema.GroupVersionKind{}, err
	}
	fqKindToRegister := schema.GroupVersionKind{}
	for _, fqKind := range fqKinds {
		if fqKind.Group == a.group.GroupVersion.Group {
			fqKindToRegister = a.group.GroupVersion.WithKind(fqKind.Kind)
			break
		}
		if fqKind.Group == "extensions" && fqKind.Kind == "ThirdPartyResourceData" {
			fqKindToRegister = a.group.GroupVersion.WithKind(fqKind.Kind)
		}
	}
	if fqKindToRegister.Empty() {
		return schema.GroupVersionKind{}, fmt.Errorf("unable to locate fully qualified kind for %v: found %v when registering for %v", reflect.TypeOf(object), fqKinds, a.group.GroupVersion)
	}
	return fqKindToRegister, nil
}
func addParams(route *restful.RouteBuilder, params []*restful.Parameter) {
	for _, param := range params {
		route.Param(param)
	}
}