| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981 |
- // Copyright (c) 2016, 2018, 2020, Oracle and/or its affiliates. All rights reserved.
- // This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
- package common
- import (
- "bytes"
- "encoding/json"
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "net/url"
- "reflect"
- "regexp"
- "strconv"
- "strings"
- "time"
- )
- ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- //Request Marshaling
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- func isNil(v reflect.Value) bool {
- return v.Kind() == reflect.Ptr && v.IsNil()
- }
- // Returns the string representation of a reflect.Value
- // Only transforms primitive values
- func toStringValue(v reflect.Value, field reflect.StructField) (string, error) {
- if v.Kind() == reflect.Ptr {
- if v.IsNil() {
- return "", fmt.Errorf("can not marshal a nil pointer")
- }
- v = v.Elem()
- }
- if v.Type() == timeType {
- t := v.Interface().(SDKTime)
- return formatTime(t), nil
- }
- if v.Type() == sdkDateType {
- t := v.Interface().(SDKDate)
- return formatDate(t), nil
- }
- switch v.Kind() {
- case reflect.Bool:
- return strconv.FormatBool(v.Bool()), nil
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- return strconv.FormatInt(v.Int(), 10), nil
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
- return strconv.FormatUint(v.Uint(), 10), nil
- case reflect.String:
- return v.String(), nil
- case reflect.Float32:
- return strconv.FormatFloat(v.Float(), 'f', -1, 32), nil
- case reflect.Float64:
- return strconv.FormatFloat(v.Float(), 'f', -1, 64), nil
- default:
- return "", fmt.Errorf("marshaling structure to a http.Request does not support field named: %s of type: %v",
- field.Name, v.Type().String())
- }
- }
- func addBinaryBody(request *http.Request, value reflect.Value, field reflect.StructField) (e error) {
- readCloser, ok := value.Interface().(io.ReadCloser)
- isMandatory, err := strconv.ParseBool(field.Tag.Get("mandatory"))
- if err != nil {
- return fmt.Errorf("mandatory tag is not valid for field %s", field.Name)
- }
- if isMandatory && !ok {
- e = fmt.Errorf("body of the request is mandatory and needs to be an io.ReadCloser interface. Can not marshal body of binary request")
- return
- }
- request.Body = readCloser
- //Set the default content type to application/octet-stream if not set
- if request.Header.Get(requestHeaderContentType) == "" {
- request.Header.Set(requestHeaderContentType, "application/octet-stream")
- }
- return nil
- }
- // getTaggedNilFieldNameOrError, evaluates if a field with json and non mandatory tags is nil
- // returns the json tag name, or an error if the tags are incorrectly present
- func getTaggedNilFieldNameOrError(field reflect.StructField, fieldValue reflect.Value) (bool, string, error) {
- currentTag := field.Tag
- jsonTag := currentTag.Get("json")
- if jsonTag == "" {
- return false, "", fmt.Errorf("json tag is not valid for field %s", field.Name)
- }
- partsJSONTag := strings.Split(jsonTag, ",")
- nameJSONField := partsJSONTag[0]
- if _, ok := currentTag.Lookup("mandatory"); !ok {
- //No mandatory field set, no-op
- return false, nameJSONField, nil
- }
- isMandatory, err := strconv.ParseBool(currentTag.Get("mandatory"))
- if err != nil {
- return false, "", fmt.Errorf("mandatory tag is not valid for field %s", field.Name)
- }
- // If the field is marked as mandatory, no-op
- if isMandatory {
- return false, nameJSONField, nil
- }
- Debugf("Adjusting tag: mandatory is false and json tag is valid on field: %s", field.Name)
- // If the field can not be nil, then no-op
- if !isNillableType(&fieldValue) {
- Debugf("WARNING json field is tagged with mandatory flags, but the type can not be nil, field name: %s", field.Name)
- return false, nameJSONField, nil
- }
- // If field value is nil, tag it as omitEmpty
- return fieldValue.IsNil(), nameJSONField, nil
- }
- // isNillableType returns true if the filed can be nil
- func isNillableType(value *reflect.Value) bool {
- k := value.Kind()
- switch k {
- case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Slice:
- return true
- }
- return false
- }
- // omitNilFieldsInJSON, removes json keys whose struct value is nil, and the field is tagged with the json and
- // mandatory:false tags
- func omitNilFieldsInJSON(data interface{}, value reflect.Value) (interface{}, error) {
- switch value.Kind() {
- case reflect.Struct:
- jsonMap := data.(map[string]interface{})
- fieldType := value.Type()
- for i := 0; i < fieldType.NumField(); i++ {
- currentField := fieldType.Field(i)
- //unexported skip
- if currentField.PkgPath != "" {
- continue
- }
- //Does not have json tag, no-op
- if _, ok := currentField.Tag.Lookup("json"); !ok {
- continue
- }
- currentFieldValue := value.Field(i)
- ok, jsonFieldName, err := getTaggedNilFieldNameOrError(currentField, currentFieldValue)
- if err != nil {
- return nil, fmt.Errorf("can not omit nil fields for field: %s, due to: %s",
- currentField.Name, err.Error())
- }
- //Delete the struct field from the json representation
- if ok {
- delete(jsonMap, jsonFieldName)
- continue
- }
- // Check to make sure the field is part of the json representation of the value
- if _, contains := jsonMap[jsonFieldName]; !contains {
- Debugf("Field %s is not present in json, omitting", jsonFieldName)
- continue
- }
- if currentFieldValue.Type() == timeType || currentFieldValue.Type() == timeTypePtr ||
- currentField.Type == sdkDateType || currentField.Type == sdkDateTypePtr {
- continue
- }
- // does it need to be adjusted?
- var adjustedValue interface{}
- adjustedValue, err = omitNilFieldsInJSON(jsonMap[jsonFieldName], currentFieldValue)
- if err != nil {
- return nil, fmt.Errorf("can not omit nil fields for field: %s, due to: %s",
- currentField.Name, err.Error())
- }
- jsonMap[jsonFieldName] = adjustedValue
- }
- return jsonMap, nil
- case reflect.Slice, reflect.Array:
- // Special case: a []byte may have been marshalled as a string
- if data != nil && reflect.TypeOf(data).Kind() == reflect.String && value.Type().Elem().Kind() == reflect.Uint8 {
- return data, nil
- }
- jsonList, ok := data.([]interface{})
- if !ok {
- return nil, fmt.Errorf("can not omit nil fields, data was expected to be a not-nil list")
- }
- newList := make([]interface{}, len(jsonList))
- var err error
- for i, val := range jsonList {
- newList[i], err = omitNilFieldsInJSON(val, value.Index(i))
- if err != nil {
- return nil, err
- }
- }
- return newList, nil
- case reflect.Map:
- jsonMap, ok := data.(map[string]interface{})
- if !ok {
- return nil, fmt.Errorf("can not omit nil fields, data was expected to be a not-nil map")
- }
- newMap := make(map[string]interface{}, len(jsonMap))
- var err error
- for key, val := range jsonMap {
- newMap[key], err = omitNilFieldsInJSON(val, value.MapIndex(reflect.ValueOf(key)))
- if err != nil {
- return nil, err
- }
- }
- return newMap, nil
- case reflect.Ptr, reflect.Interface:
- valPtr := value.Elem()
- return omitNilFieldsInJSON(data, valPtr)
- default:
- //Otherwise no-op
- return data, nil
- }
- }
- // removeNilFieldsInJSONWithTaggedStruct remove struct fields tagged with json and mandatory false
- // that are nil
- func removeNilFieldsInJSONWithTaggedStruct(rawJSON []byte, value reflect.Value) ([]byte, error) {
- var rawInterface interface{}
- decoder := json.NewDecoder(bytes.NewBuffer(rawJSON))
- decoder.UseNumber()
- var err error
- if err = decoder.Decode(&rawInterface); err != nil {
- return nil, err
- }
- fixedMap, err := omitNilFieldsInJSON(rawInterface, value)
- if err != nil {
- return nil, err
- }
- return json.Marshal(fixedMap)
- }
- func addToBody(request *http.Request, value reflect.Value, field reflect.StructField) (e error) {
- Debugln("Marshaling to body from field:", field.Name)
- if request.Body != nil {
- Logf("The body of the request is already set. Structure: %s will overwrite it\n", field.Name)
- }
- tag := field.Tag
- encoding := tag.Get("encoding")
- if encoding == "binary" {
- return addBinaryBody(request, value, field)
- }
- rawJSON, e := json.Marshal(value.Interface())
- if e != nil {
- return
- }
- marshaled, e := removeNilFieldsInJSONWithTaggedStruct(rawJSON, value)
- if e != nil {
- return
- }
- if defaultLogger.LogLevel() == verboseLogging {
- Debugf("Marshaled body is: %s\n", string(marshaled))
- }
- bodyBytes := bytes.NewReader(marshaled)
- request.ContentLength = int64(bodyBytes.Len())
- request.Header.Set(requestHeaderContentLength, strconv.FormatInt(request.ContentLength, 10))
- request.Header.Set(requestHeaderContentType, "application/json")
- request.Body = ioutil.NopCloser(bodyBytes)
- request.GetBody = func() (io.ReadCloser, error) {
- return ioutil.NopCloser(bodyBytes), nil
- }
- return
- }
- func addToQuery(request *http.Request, value reflect.Value, field reflect.StructField) (e error) {
- Debugln("Marshaling to query from field: ", field.Name)
- if request.URL == nil {
- request.URL = &url.URL{}
- }
- query := request.URL.Query()
- var queryParameterValue, queryParameterName string
- if queryParameterName = field.Tag.Get("name"); queryParameterName == "" {
- return fmt.Errorf("marshaling request to a query requires the 'name' tag for field: %s ", field.Name)
- }
- mandatory, _ := strconv.ParseBool(strings.ToLower(field.Tag.Get("mandatory")))
- //If mandatory and nil. Error out
- if mandatory && isNil(value) {
- return fmt.Errorf("marshaling request to a header requires not nil pointer for field: %s", field.Name)
- }
- //if not mandatory and nil. Omit
- if !mandatory && isNil(value) {
- Debugf("Query parameter value is not mandatory and is nil pointer in field: %s. Skipping query", field.Name)
- return
- }
- encoding := strings.ToLower(field.Tag.Get("collectionFormat"))
- var collectionFormatStringValues []string
- switch encoding {
- case "csv", "multi":
- if value.Kind() != reflect.Slice && value.Kind() != reflect.Array {
- e = fmt.Errorf("query parameter is tagged as csv or multi yet its type is neither an Array nor a Slice: %s", field.Name)
- break
- }
- numOfElements := value.Len()
- collectionFormatStringValues = make([]string, numOfElements)
- for i := 0; i < numOfElements; i++ {
- collectionFormatStringValues[i], e = toStringValue(value.Index(i), field)
- if e != nil {
- break
- }
- }
- queryParameterValue = strings.Join(collectionFormatStringValues, ",")
- case "":
- queryParameterValue, e = toStringValue(value, field)
- default:
- e = fmt.Errorf("encoding of type %s is not supported for query param: %s", encoding, field.Name)
- }
- if e != nil {
- return
- }
- //check for tag "omitEmpty", this is done to accomodate unset fields that do not
- //support an empty string: enums in query params
- if omitEmpty, present := field.Tag.Lookup("omitEmpty"); present {
- omitEmptyBool, _ := strconv.ParseBool(strings.ToLower(omitEmpty))
- if queryParameterValue != "" || !omitEmptyBool {
- addToQueryForEncoding(&query, encoding, queryParameterName, queryParameterValue, collectionFormatStringValues)
- } else {
- Debugf("Omitting %s, is empty and omitEmpty tag is set", field.Name)
- }
- } else {
- addToQueryForEncoding(&query, encoding, queryParameterName, queryParameterValue, collectionFormatStringValues)
- }
- request.URL.RawQuery = query.Encode()
- return
- }
- func addToQueryForEncoding(query *url.Values, encoding string, queryParameterName string, queryParameterValue string, collectionFormatStringValues []string) {
- if encoding == "multi" {
- for _, stringValue := range collectionFormatStringValues {
- query.Add(queryParameterName, stringValue)
- }
- } else {
- query.Set(queryParameterName, queryParameterValue)
- }
- }
- // Adds to the path of the url in the order they appear in the structure
- func addToPath(request *http.Request, value reflect.Value, field reflect.StructField) (e error) {
- var additionalURLPathPart string
- if additionalURLPathPart, e = toStringValue(value, field); e != nil {
- return fmt.Errorf("can not marshal to path in request for field %s. Due to %s", field.Name, e.Error())
- }
- // path should not be empty for any operations
- if len(additionalURLPathPart) == 0 {
- return fmt.Errorf("value cannot be empty for field %s in path", field.Name)
- }
- if request.URL == nil {
- request.URL = &url.URL{}
- request.URL.Path = ""
- }
- var currentURLPath = request.URL.Path
- var templatedPathRegex, _ = regexp.Compile(".*{.+}.*")
- if !templatedPathRegex.MatchString(currentURLPath) {
- Debugln("Marshaling request to path by appending field:", field.Name)
- allPath := []string{currentURLPath, additionalURLPathPart}
- request.URL.Path = strings.Join(allPath, "/")
- } else {
- var fieldName string
- if fieldName = field.Tag.Get("name"); fieldName == "" {
- e = fmt.Errorf("marshaling request to path name and template requires a 'name' tag for field: %s", field.Name)
- return
- }
- urlTemplate := currentURLPath
- Debugln("Marshaling to path from field: ", field.Name, " in template: ", urlTemplate)
- request.URL.Path = strings.Replace(urlTemplate, "{"+fieldName+"}", additionalURLPathPart, -1)
- }
- return
- }
- func setWellKnownHeaders(request *http.Request, headerName, headerValue string) (e error) {
- switch strings.ToLower(headerName) {
- case "content-length":
- var len int
- len, e = strconv.Atoi(headerValue)
- if e != nil {
- return
- }
- request.ContentLength = int64(len)
- }
- return nil
- }
- func addToHeader(request *http.Request, value reflect.Value, field reflect.StructField) (e error) {
- Debugln("Marshaling to header from field: ", field.Name)
- if request.Header == nil {
- request.Header = http.Header{}
- }
- var headerName, headerValue string
- if headerName = field.Tag.Get("name"); headerName == "" {
- return fmt.Errorf("marshaling request to a header requires the 'name' tag for field: %s", field.Name)
- }
- mandatory, _ := strconv.ParseBool(strings.ToLower(field.Tag.Get("mandatory")))
- //If mandatory and nil. Error out
- if mandatory && isNil(value) {
- return fmt.Errorf("marshaling request to a header requires not nil pointer for field: %s", field.Name)
- }
- // generate opc-request-id if header value is nil and header name matches
- value = generateOpcRequestID(headerName, value)
- //if not mandatory and nil. Omit
- if !mandatory && isNil(value) {
- Debugf("Header value is not mandatory and is nil pointer in field: %s. Skipping header", field.Name)
- return
- }
- //Otherwise get value and set header
- if headerValue, e = toStringValue(value, field); e != nil {
- return
- }
- if e = setWellKnownHeaders(request, headerName, headerValue); e != nil {
- return
- }
- if isUniqueHeaderRequired(headerName) {
- request.Header.Set(headerName, headerValue)
- } else {
- request.Header.Add(headerName, headerValue)
- }
- return
- }
- // Check if the header is required to be unique
- func isUniqueHeaderRequired(headerName string) bool {
- return strings.EqualFold(headerName, requestHeaderContentType)
- }
- // Header collection is a map of string to string that gets rendered as individual headers with a given prefix
- func addToHeaderCollection(request *http.Request, value reflect.Value, field reflect.StructField) (e error) {
- Debugln("Marshaling to header-collection from field:", field.Name)
- if request.Header == nil {
- request.Header = http.Header{}
- }
- var headerPrefix string
- if headerPrefix = field.Tag.Get("prefix"); headerPrefix == "" {
- return fmt.Errorf("marshaling request to a header requires the 'prefix' tag for field: %s", field.Name)
- }
- mandatory, _ := strconv.ParseBool(strings.ToLower(field.Tag.Get("mandatory")))
- //If mandatory and nil. Error out
- if mandatory && isNil(value) {
- return fmt.Errorf("marshaling request to a header requires not nil pointer for field: %s", field.Name)
- }
- //if not mandatory and nil. Omit
- if !mandatory && isNil(value) {
- Debugf("Header value is not mandatory and is nil pointer in field: %s. Skipping header", field.Name)
- return
- }
- //cast to map
- headerValues, ok := value.Interface().(map[string]string)
- if !ok {
- e = fmt.Errorf("header fields need to be of type map[string]string")
- return
- }
- for k, v := range headerValues {
- headerName := fmt.Sprintf("%s%s", headerPrefix, k)
- request.Header.Set(headerName, v)
- }
- return
- }
- // Makes sure the incoming structure is able to be marshalled
- // to a request
- func checkForValidRequestStruct(s interface{}) (*reflect.Value, error) {
- val := reflect.ValueOf(s)
- for val.Kind() == reflect.Ptr {
- if val.IsNil() {
- return nil, fmt.Errorf("can not marshal to request a pointer to structure")
- }
- val = val.Elem()
- }
- if s == nil {
- return nil, fmt.Errorf("can not marshal to request a nil structure")
- }
- if val.Kind() != reflect.Struct {
- return nil, fmt.Errorf("can not marshal to request, expects struct input. Got %v", val.Kind())
- }
- return &val, nil
- }
- // Populates the parts of a request by reading tags in the passed structure
- // nested structs are followed recursively depth-first.
- func structToRequestPart(request *http.Request, val reflect.Value) (err error) {
- typ := val.Type()
- for i := 0; i < typ.NumField(); i++ {
- if err != nil {
- return
- }
- sf := typ.Field(i)
- //unexported
- if sf.PkgPath != "" && !sf.Anonymous {
- continue
- }
- sv := val.Field(i)
- tag := sf.Tag.Get("contributesTo")
- switch tag {
- case "header":
- err = addToHeader(request, sv, sf)
- case "header-collection":
- err = addToHeaderCollection(request, sv, sf)
- case "path":
- err = addToPath(request, sv, sf)
- case "query":
- err = addToQuery(request, sv, sf)
- case "body":
- err = addToBody(request, sv, sf)
- case "":
- Debugln(sf.Name, " does not contain contributes tag. Skipping.")
- default:
- err = fmt.Errorf("can not marshal field: %s. It needs to contain valid contributesTo tag", sf.Name)
- }
- }
- //If headers are and the content type was not set, we default to application/json
- if request.Header != nil && request.Header.Get(requestHeaderContentType) == "" {
- request.Header.Set(requestHeaderContentType, "application/json")
- }
- return
- }
- // HTTPRequestMarshaller marshals a structure to an http request using tag values in the struct
- // The marshaller tag should like the following
- // type A struct {
- // ANumber string `contributesTo="query" name="number"`
- // TheBody `contributesTo="body"`
- // }
- // where the contributesTo tag can be: header, path, query, body
- // and the 'name' tag is the name of the value used in the http request(not applicable for path)
- // If path is specified as part of the tag, the values are appened to the url path
- // in the order they appear in the structure
- // The current implementation only supports primitive types, except for the body tag, which needs a struct type.
- // The body of a request will be marshaled using the tags of the structure
- func HTTPRequestMarshaller(requestStruct interface{}, httpRequest *http.Request) (err error) {
- var val *reflect.Value
- if val, err = checkForValidRequestStruct(requestStruct); err != nil {
- return
- }
- Debugln("Marshaling to Request: ", val.Type().Name())
- err = structToRequestPart(httpRequest, *val)
- return
- }
- // MakeDefaultHTTPRequest creates the basic http request with the necessary headers set
- func MakeDefaultHTTPRequest(method, path string) (httpRequest http.Request) {
- httpRequest = http.Request{
- Proto: "HTTP/1.1",
- ProtoMajor: 1,
- ProtoMinor: 1,
- Header: make(http.Header),
- URL: &url.URL{},
- }
- httpRequest.Header.Set(requestHeaderContentLength, "0")
- httpRequest.Header.Set(requestHeaderDate, time.Now().UTC().Format(http.TimeFormat))
- httpRequest.Header.Set(requestHeaderOpcClientInfo, strings.Join([]string{defaultSDKMarker, Version()}, "/"))
- httpRequest.Header.Set(requestHeaderAccept, "*/*")
- httpRequest.Method = method
- httpRequest.URL.Path = path
- return
- }
- // MakeDefaultHTTPRequestWithTaggedStruct creates an http request from an struct with tagged fields, see HTTPRequestMarshaller
- // for more information
- func MakeDefaultHTTPRequestWithTaggedStruct(method, path string, requestStruct interface{}) (httpRequest http.Request, err error) {
- httpRequest = MakeDefaultHTTPRequest(method, path)
- err = HTTPRequestMarshaller(requestStruct, &httpRequest)
- return
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- //Request UnMarshaling
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- // Makes sure the incoming structure is able to be unmarshaled
- // to a request
- func checkForValidResponseStruct(s interface{}) (*reflect.Value, error) {
- val := reflect.ValueOf(s)
- for val.Kind() == reflect.Ptr {
- if val.IsNil() {
- return nil, fmt.Errorf("can not unmarshal to response a pointer to nil structure")
- }
- val = val.Elem()
- }
- if s == nil {
- return nil, fmt.Errorf("can not unmarshal to response a nil structure")
- }
- if val.Kind() != reflect.Struct {
- return nil, fmt.Errorf("can not unmarshal to response, expects struct input. Got %v", val.Kind())
- }
- return &val, nil
- }
- func intSizeFromKind(kind reflect.Kind) int {
- switch kind {
- case reflect.Int8, reflect.Uint8:
- return 8
- case reflect.Int16, reflect.Uint16:
- return 16
- case reflect.Int32, reflect.Uint32:
- return 32
- case reflect.Int64, reflect.Uint64:
- return 64
- case reflect.Int, reflect.Uint:
- return strconv.IntSize
- default:
- Debugf("The type is not valid: %v. Returing int size for arch\n", kind.String())
- return strconv.IntSize
- }
- }
- func analyzeValue(stringValue string, kind reflect.Kind, field reflect.StructField) (val reflect.Value, valPointer reflect.Value, err error) {
- switch kind {
- case timeType.Kind():
- var t time.Time
- t, err = tryParsingTimeWithValidFormatsForHeaders([]byte(stringValue), field.Name)
- if err != nil {
- return
- }
- sdkTime := sdkTimeFromTime(t)
- val = reflect.ValueOf(sdkTime)
- valPointer = reflect.ValueOf(&sdkTime)
- return
- case sdkDateType.Kind():
- var t time.Time
- t, err = tryParsingTimeWithValidFormatsForHeaders([]byte(stringValue), field.Name)
- if err != nil {
- return
- }
- sdkDate := sdkDateFromTime(t)
- val = reflect.ValueOf(sdkDate)
- valPointer = reflect.ValueOf(&sdkDate)
- return
- case reflect.Bool:
- var bVal bool
- if bVal, err = strconv.ParseBool(stringValue); err != nil {
- return
- }
- val = reflect.ValueOf(bVal)
- valPointer = reflect.ValueOf(&bVal)
- return
- case reflect.Int:
- size := intSizeFromKind(kind)
- var iVal int64
- if iVal, err = strconv.ParseInt(stringValue, 10, size); err != nil {
- return
- }
- var iiVal int
- iiVal = int(iVal)
- val = reflect.ValueOf(iiVal)
- valPointer = reflect.ValueOf(&iiVal)
- return
- case reflect.Int64:
- size := intSizeFromKind(kind)
- var iVal int64
- if iVal, err = strconv.ParseInt(stringValue, 10, size); err != nil {
- return
- }
- val = reflect.ValueOf(iVal)
- valPointer = reflect.ValueOf(&iVal)
- return
- case reflect.Uint:
- size := intSizeFromKind(kind)
- var iVal uint64
- if iVal, err = strconv.ParseUint(stringValue, 10, size); err != nil {
- return
- }
- var uiVal uint
- uiVal = uint(iVal)
- val = reflect.ValueOf(uiVal)
- valPointer = reflect.ValueOf(&uiVal)
- return
- case reflect.String:
- val = reflect.ValueOf(stringValue)
- valPointer = reflect.ValueOf(&stringValue)
- case reflect.Float32:
- var fVal float64
- if fVal, err = strconv.ParseFloat(stringValue, 32); err != nil {
- return
- }
- var ffVal float32
- ffVal = float32(fVal)
- val = reflect.ValueOf(ffVal)
- valPointer = reflect.ValueOf(&ffVal)
- return
- case reflect.Float64:
- var fVal float64
- if fVal, err = strconv.ParseFloat(stringValue, 64); err != nil {
- return
- }
- val = reflect.ValueOf(fVal)
- valPointer = reflect.ValueOf(&fVal)
- return
- default:
- err = fmt.Errorf("value for kind: %s not supported", kind)
- }
- return
- }
- // Sets the field of a struct, with the appropiate value of the string
- // Only sets basic types
- func fromStringValue(newValue string, val *reflect.Value, field reflect.StructField) (err error) {
- if !val.CanSet() {
- err = fmt.Errorf("can not set field name: %s of type: %v", field.Name, val.Type().String())
- return
- }
- kind := val.Kind()
- isPointer := false
- if val.Kind() == reflect.Ptr {
- isPointer = true
- kind = field.Type.Elem().Kind()
- }
- value, valPtr, err := analyzeValue(newValue, kind, field)
- if err != nil {
- return
- }
- if !isPointer {
- val.Set(value)
- } else {
- val.Set(valPtr)
- }
- return
- }
- // PolymorphicJSONUnmarshaler is the interface to unmarshal polymorphic json payloads
- type PolymorphicJSONUnmarshaler interface {
- UnmarshalPolymorphicJSON(data []byte) (interface{}, error)
- }
- func valueFromPolymorphicJSON(content []byte, unmarshaler PolymorphicJSONUnmarshaler) (val interface{}, err error) {
- err = json.Unmarshal(content, unmarshaler)
- if err != nil {
- return
- }
- val, err = unmarshaler.UnmarshalPolymorphicJSON(content)
- return
- }
- func valueFromJSONBody(response *http.Response, value *reflect.Value, unmarshaler PolymorphicJSONUnmarshaler) (val interface{}, err error) {
- //Consumes the body, consider implementing it
- //without body consumption
- var content []byte
- content, err = ioutil.ReadAll(response.Body)
- if err != nil {
- return
- }
- if unmarshaler != nil {
- val, err = valueFromPolymorphicJSON(content, unmarshaler)
- return
- }
- val = reflect.New(value.Type()).Interface()
- err = json.Unmarshal(content, &val)
- return
- }
- func addFromBody(response *http.Response, value *reflect.Value, field reflect.StructField, unmarshaler PolymorphicJSONUnmarshaler) (err error) {
- Debugln("Unmarshaling from body to field: ", field.Name)
- if response.Body == nil {
- Debugln("Unmarshaling body skipped due to nil body content for field: ", field.Name)
- return nil
- }
- tag := field.Tag
- encoding := tag.Get("encoding")
- var iVal interface{}
- switch encoding {
- case "binary":
- value.Set(reflect.ValueOf(response.Body))
- return
- case "plain-text":
- //Expects UTF-8
- byteArr, e := ioutil.ReadAll(response.Body)
- if e != nil {
- return e
- }
- str := string(byteArr)
- value.Set(reflect.ValueOf(&str))
- return
- default: //If the encoding is not set. we'll decode with json
- iVal, err = valueFromJSONBody(response, value, unmarshaler)
- if err != nil {
- return
- }
- newVal := reflect.ValueOf(iVal)
- if newVal.Kind() == reflect.Ptr {
- newVal = newVal.Elem()
- }
- value.Set(newVal)
- return
- }
- }
- func addFromHeader(response *http.Response, value *reflect.Value, field reflect.StructField) (err error) {
- Debugln("Unmarshaling from header to field: ", field.Name)
- var headerName string
- if headerName = field.Tag.Get("name"); headerName == "" {
- return fmt.Errorf("unmarshaling response to a header requires the 'name' tag for field: %s", field.Name)
- }
- headerValue := response.Header.Get(headerName)
- if headerValue == "" {
- Debugf("Unmarshalling did not find header with name:%s", headerName)
- return nil
- }
- if err = fromStringValue(headerValue, value, field); err != nil {
- return fmt.Errorf("unmarshaling response to a header failed for field %s, due to %s", field.Name,
- err.Error())
- }
- return
- }
- func addFromHeaderCollection(response *http.Response, value *reflect.Value, field reflect.StructField) error {
- Debugln("Unmarshaling from header-collection to field:", field.Name)
- var headerPrefix string
- if headerPrefix = field.Tag.Get("prefix"); headerPrefix == "" {
- return fmt.Errorf("Unmarshaling response to a header-collection requires the 'prefix' tag for field: %s", field.Name)
- }
- mapCollection := make(map[string]string)
- for name, value := range response.Header {
- nameLowerCase := strings.ToLower(name)
- if strings.HasPrefix(nameLowerCase, headerPrefix) {
- headerNoPrefix := strings.TrimPrefix(nameLowerCase, headerPrefix)
- mapCollection[headerNoPrefix] = value[0]
- }
- }
- Debugln("Marshalled header collection is:", mapCollection)
- value.Set(reflect.ValueOf(mapCollection))
- return nil
- }
- // Populates a struct from parts of a request by reading tags of the struct
- func responseToStruct(response *http.Response, val *reflect.Value, unmarshaler PolymorphicJSONUnmarshaler) (err error) {
- typ := val.Type()
- for i := 0; i < typ.NumField(); i++ {
- if err != nil {
- return
- }
- sf := typ.Field(i)
- //unexported
- if sf.PkgPath != "" {
- continue
- }
- sv := val.Field(i)
- tag := sf.Tag.Get("presentIn")
- switch tag {
- case "header":
- err = addFromHeader(response, &sv, sf)
- case "header-collection":
- err = addFromHeaderCollection(response, &sv, sf)
- case "body":
- err = addFromBody(response, &sv, sf, unmarshaler)
- case "":
- Debugln(sf.Name, " does not contain presentIn tag. Skipping")
- default:
- err = fmt.Errorf("can not unmarshal field: %s. It needs to contain valid presentIn tag", sf.Name)
- }
- }
- return
- }
- // UnmarshalResponse hydrates the fields of a struct with the values of a http response, guided
- // by the field tags. The directive tag is "presentIn" and it can be either
- // - "header": Will look for the header tagged as "name" in the headers of the struct and set it value to that
- // - "body": It will try to marshal the body from a json string to a struct tagged with 'presentIn: "body"'.
- // Further this method will consume the body it should be safe to close it after this function
- // Notice the current implementation only supports native types:int, strings, floats, bool as the field types
- func UnmarshalResponse(httpResponse *http.Response, responseStruct interface{}) (err error) {
- var val *reflect.Value
- if val, err = checkForValidResponseStruct(responseStruct); err != nil {
- return
- }
- if err = responseToStruct(httpResponse, val, nil); err != nil {
- return
- }
- return nil
- }
- // UnmarshalResponseWithPolymorphicBody similar to UnmarshalResponse but assumes the body of the response
- // contains polymorphic json. This function will use the unmarshaler argument to unmarshal json content
- func UnmarshalResponseWithPolymorphicBody(httpResponse *http.Response, responseStruct interface{}, unmarshaler PolymorphicJSONUnmarshaler) (err error) {
- var val *reflect.Value
- if val, err = checkForValidResponseStruct(responseStruct); err != nil {
- return
- }
- if err = responseToStruct(httpResponse, val, unmarshaler); err != nil {
- return
- }
- return nil
- }
- // generate request id if user not provided and for each retry operation re-gen a new request id
- func generateOpcRequestID(headerName string, value reflect.Value) (newValue reflect.Value) {
- newValue = value
- isNilValue := isNil(newValue)
- isOpcRequestIDHeader := headerName == requestHeaderOpcRequestID || headerName == requestHeaderOpcClientRequestID
- if isNilValue && isOpcRequestIDHeader {
- requestID, err := generateRandUUID()
- if err != nil {
- // this will not fail the request, just skip add opc-request-id
- Debugf("unable to generate opc-request-id. %s", err.Error())
- } else {
- newValue = reflect.ValueOf(String(requestID))
- Debugf("add request id for header: %s, with value: %s", headerName, requestID)
- }
- }
- return
- }
|