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
}
FormatRequest maps a map of typed values to an HTTP request
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
}