Add DNS Provider for Oracle Cloud (#808)
This commit is contained in:
parent
a144800896
commit
af67943e94
89 changed files with 9842 additions and 0 deletions
961
vendor/github.com/oracle/oci-go-sdk/common/http.go
generated
vendored
Normal file
961
vendor/github.com/oracle/oci-go-sdk/common/http.go
generated
vendored
Normal file
|
@ -0,0 +1,961 @@
|
|||
// Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
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', 6, 32), nil
|
||||
case reflect.Float64:
|
||||
return strconv.FormatFloat(v.Float(), 'f', 6, 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) (e error) {
|
||||
readCloser, ok := value.Interface().(io.ReadCloser)
|
||||
if !ok {
|
||||
e = fmt.Errorf("body of the request 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)
|
||||
}
|
||||
|
||||
rawJSON, e := json.Marshal(value.Interface())
|
||||
if e != nil {
|
||||
return
|
||||
}
|
||||
marshaled, e := removeNilFieldsInJSONWithTaggedStruct(rawJSON, value)
|
||||
if e != nil {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
request.Header.Add(headerName, headerValue)
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue