func (h *HTTPMapper) formatQuery(inputs map[string]*typedvalues.TypedValue) url.Values {
	queryInput := inputs[types.InputQuery]
	if queryInput == nil {
		return nil
	}
	target := url.Values{}
	i, err := typedvalues.Unwrap(queryInput)
	if err != nil {
		logrus.Errorf("Failed to format headers: %v", err)
	}
	switch i.(type) {
	case map[string]interface{}:
		for k, v := range i.(map[string]interface{}) {
			target.Add(k, fmt.Sprintf("%v", v))
		}
	default:
		logrus.Warnf("Ignoring invalid type of query input (expected map[string]interface{}, was %v)",
			reflect.TypeOf(i))
	}
	return target
}
FUTURE: support multivalued query params
package httpconv
import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"reflect"
	"github.com/fission/fission-workflows/pkg/types"
	"github.com/fission/fission-workflows/pkg/types/typedvalues"
	"github.com/fission/fission-workflows/pkg/util/mediatype"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)
const (
	inputContentType  = "content-type"
	headerContentType = "Content-Type"
)
var DefaultHTTPMapper = &HTTPMapper{
	DefaultHTTPMethod: http.MethodPost,
	ValueTypeResolver: func(tv *typedvalues.TypedValue) *mediatype.MediaType {
		if tv == nil {
			return MediaTypeBytes
		}
		if ct, ok := tv.GetMetadataValue(headerContentType); ok {
			mt, err := mediatype.Parse(ct)
			if err == nil {
				return mt
			}
		}
		switch tv.ValueType() {
		case typedvalues.TypeBytes:
			return MediaTypeBytes
		case typedvalues.TypeNil:
			return MediaTypeBytes
		case typedvalues.TypeString:
			return MediaTypeText
		}
		return MediaTypeJSON
	},
	MediaTypeResolver: func(mt *mediatype.MediaType) ParserFormatter {
		if mt == nil {
			return bytesMapper
		}
		switch mt.Identifier() {
		case MediaTypeJSON.String(), "text/json":
			return jsonMapper
		case MediaTypeText.String():
			return textMapper
		case MediaTypeBytes.String():
			return bytesMapper
		case MediaTypeProtobuf.String(), "application/vnd.google.protobuf", "application/x.google.protobuf",
			"application/x.protobuf":
			return protobufMapper
		}
		if mt.Type == "text" {
			return textMapper
		}
		if mt.Type == "application" {
			return bytesMapper
		}
		return bytesMapper
	},
}
func ParseRequest(req *http.Request) (map[string]*typedvalues.TypedValue, error) {
	return DefaultHTTPMapper.ParseRequest(req)
}
func ParseResponse(resp *http.Response) (*typedvalues.TypedValue, error) {
	return DefaultHTTPMapper.ParseResponse(resp)
}
func ParseResponseHeaders(resp *http.Response) *typedvalues.TypedValue {
	return DefaultHTTPMapper.ParseResponseHeaders(resp)
}
func FormatRequest(source map[string]*typedvalues.TypedValue, target *http.Request) error {
	return DefaultHTTPMapper.FormatRequest(source, target)
}
func FormatResponse(w http.ResponseWriter, output *typedvalues.TypedValue, outputHeaders *typedvalues.TypedValue, outputErr *types.Error) {
	DefaultHTTPMapper.FormatResponse(w, output, outputHeaders, outputErr)
}
type HTTPMapper struct {
	DefaultHTTPMethod string
	ValueTypeResolver func(tv *typedvalues.TypedValue) *mediatype.MediaType
	DefaultMediaType  *mediatype.MediaType
	MediaTypeResolver func(mediaType *mediatype.MediaType) ParserFormatter
}
func (h *HTTPMapper) ParseResponse(resp *http.Response) (*typedvalues.TypedValue, error) {
	contentType := h.getRequestContentType(resp.Header)
	defer resp.Body.Close()
	return DefaultHTTPMapper.parseBody(resp.Body, contentType)
}
func (h *HTTPMapper) ParseResponseHeaders(resp *http.Response) *typedvalues.TypedValue {
	return DefaultHTTPMapper.parseRespHeaders(resp)
}
func (h *HTTPMapper) ParseRequest(req *http.Request) (map[string]*typedvalues.TypedValue, error) {
	contentType := h.getRequestContentType(req.Header)
	defer req.Body.Close()
	var body *typedvalues.TypedValue
	var err error
	switch contentType.Identifier() {
	case "application/x-www-form-urlencoded":
		req.ParseForm()
		mp := map[string]interface{}{}
		for k, vs := range req.Form {
			if len(vs) > 0 {
				mp[k] = vs[0]
			}
		}
		body, err = typedvalues.Wrap(mp)
		if err != nil {
			return nil, errors.Errorf("failed to parse form request: %v", err)
		}
	default:
		body, err = h.parseBody(req.Body, contentType)
		if err != nil {
			return nil, errors.Errorf("failed to parse request: %v", err)
		}
	}
	return map[string]*typedvalues.TypedValue{
		types.InputBody: body,
		types.InputMain: body,
		types.InputQuery: h.parseQuery(req),
		types.InputHeaders: h.parseReqHeaders(req),
		types.InputMethod: h.parseMethod(req),
	}, nil
}
func (h *HTTPMapper) FormatResponse(w http.ResponseWriter, output *typedvalues.TypedValue, outputHeaders *typedvalues.TypedValue, outputErr *types.Error) {
	if w == nil {
		panic("cannot format response to nil")
	}
	if outputErr != nil {
		http.Error(w, outputErr.Error(), http.StatusInternalServerError)
		return
	}
	if output == nil {
		w.WriteHeader(http.StatusNoContent)
		output = typedvalues.MustWrap(nil)
		return
	}
	headers := h.formatHeaders(outputHeaders)
	for k, v := range headers {
		if len(v) > 0 {
			w.Header().Set(k, v[0])
		}
	}
	w.WriteHeader(http.StatusOK)
	contentType := h.ValueTypeResolver(output)
	err := h.formatBody(w, output, contentType)
	if err != nil {
		h.FormatResponse(w, nil, nil, &types.Error{
			Message: fmt.Sprintf("Failed to format response body: %v", err),
		})
	}
	return
}
func (h *HTTPMapper) FormatRequest(source map[string]*typedvalues.TypedValue, target *http.Request) error {
	if target == nil {
		panic("cannot format request to nil")
	}
	contentType := h.determineContentTypeFromInputs(source)
	mainInput := getFirstDefined(source, types.InputBody, types.InputMain)
	if mainInput != nil {
		err := h.formatBody(&requestWriter{req: target}, mainInput, contentType)
		if err != nil {
			return err
		}
	}
	method := h.formatMethod(source)
	target.Method = method
	query := h.formatQuery(source)
	if query != nil {
		if target.URL == nil {
			panic("request has no URL")
		}
		target.URL.RawQuery = query.Encode()
	}
	headers := http.Header{}
	rawHeaders, ok := source[types.InputHeaders]
	if ok && rawHeaders != nil {
		headers = h.formatHeaders(rawHeaders)
	}
	if target.Header == nil {
		target.Header = headers
	} else {
		for k, v := range headers {
			if len(v) > 0 {
				target.Header.Set(k, v[0])
			}
		}
	}
	return nil
}
func (h *HTTPMapper) Clone() *HTTPMapper {
	return &HTTPMapper{
		DefaultMediaType:  h.DefaultMediaType.Copy(),
		DefaultHTTPMethod: h.DefaultHTTPMethod,
		ValueTypeResolver: h.ValueTypeResolver,
		MediaTypeResolver: h.MediaTypeResolver,
	}
}
func (h *HTTPMapper) parseBody(data io.Reader, contentType *mediatype.MediaType) (*typedvalues.TypedValue, error) {
	if contentType == nil {
		contentType = h.DefaultMediaType
	}
	return h.MediaTypeResolver(contentType).Parse(contentType, data)
}
func (h *HTTPMapper) parseMethod(r *http.Request) *typedvalues.TypedValue {
	return typedvalues.MustWrap(r.Method)
}
func (h *HTTPMapper) parseReqHeaders(r *http.Request) *typedvalues.TypedValue {
	headers := flattenMultimap(r.Header)
	return typedvalues.MustWrap(headers)
}
func (h *HTTPMapper) parseRespHeaders(r *http.Response) *typedvalues.TypedValue {
	headers := flattenMultimap(r.Header)
	return typedvalues.MustWrap(headers)
}
func (h *HTTPMapper) parseQuery(r *http.Request) *typedvalues.TypedValue {
	query := flattenMultimap(r.URL.Query())
	return typedvalues.MustWrap(query)
}
func (h *HTTPMapper) formatMethod(inputs map[string]*typedvalues.TypedValue) string {
	tv, ok := inputs[types.InputMethod]
	if ok && tv != nil {
		contentType, err := typedvalues.UnwrapString(tv)
		if err == nil {
			return contentType
		}
		logrus.Errorf("Invalid method in inputs: %+v", tv)
	}
	return h.DefaultHTTPMethod
}