github.com/opentelekomcloud/gophertelekomcloud@v0.9.3/internal/build/headers.go (about)

     1  package build
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strconv"
     7  
     8  	"github.com/opentelekomcloud/gophertelekomcloud/internal/multierr"
     9  )
    10  
    11  /*
    12  Headers is an internal function to be used by request methods in
    13  individual resource packages.
    14  
    15  It accepts an arbitrary tagged structure and produces a string map that's
    16  suitable for use as the HTTP headers of an outgoing request. Field names are
    17  mapped to header names based in "h" tags.
    18  
    19  	type struct QueryStruct {
    20  	  Bar string `h:"x_bar"`
    21  	  Baz int    `h:"lorem_ipsum"`
    22  	}
    23  
    24  	instance := QueryStruct{
    25  	  Bar: "AAA",
    26  	  Baz: "BBB",
    27  	}
    28  
    29  will be converted into:
    30  
    31  	map[string]string{
    32  	  "x_bar": "AAA",
    33  	  "lorem_ipsum": "BBB",
    34  	}
    35  
    36  Untagged fields and fields left at their zero values are skipped. Integers,
    37  booleans and string values are supported.
    38  */
    39  func Headers(opts interface{}) (map[string]string, error) {
    40  	if opts == nil {
    41  		return nil, fmt.Errorf("error building headers: %w", ErrNilOpts)
    42  	}
    43  
    44  	optsValue := reflect.ValueOf(opts)
    45  	if optsValue.Kind() == reflect.Ptr {
    46  		optsValue = optsValue.Elem()
    47  	}
    48  
    49  	optsType := reflect.TypeOf(opts)
    50  	if optsType.Kind() == reflect.Ptr {
    51  		optsType = optsType.Elem()
    52  	}
    53  
    54  	if optsValue.Kind() != reflect.Struct {
    55  		// Return an error if the underlying type of 'opts' isn't a struct.
    56  		return nil, fmt.Errorf("error building headers: options type is not a struct")
    57  	}
    58  
    59  	mErr := multierr.MultiError{}
    60  	result := make(map[string]string)
    61  
    62  	for i := 0; i < optsValue.NumField(); i++ {
    63  		value := optsValue.Field(i)
    64  		field := optsType.Field(i)
    65  
    66  		headerName := field.Tag.Get("h")
    67  		if headerName == "" {
    68  			continue
    69  		}
    70  
    71  		if value.IsZero() {
    72  			// We duplicate the check from ValidateTags to avoid double reflect package usage
    73  			// TODO: investigate performance difference when using ValidateTags
    74  			if structFieldRequired(field) {
    75  				mErr = append(mErr, fmt.Errorf("required header [%s] not set", field.Name))
    76  			}
    77  			continue
    78  		}
    79  
    80  		if value.Kind() == reflect.Ptr {
    81  			value = value.Elem()
    82  		}
    83  
    84  		var headerValue string
    85  
    86  		// if the field is set, add it to the slice of query pieces
    87  		switch value.Kind() {
    88  		case reflect.String:
    89  			headerValue = value.String()
    90  		case reflect.Int, reflect.Int32, reflect.Int64:
    91  			headerValue = strconv.FormatInt(value.Int(), 10)
    92  		case reflect.Bool:
    93  			headerValue = strconv.FormatBool(value.Bool())
    94  		default:
    95  			mErr = append(mErr, fmt.Errorf("value of unsupported type %s", value.Type()))
    96  		}
    97  
    98  		result[headerName] = headerValue
    99  	}
   100  
   101  	if err := mErr.ErrorOrNil(); err != nil {
   102  		return nil, fmt.Errorf("error building headers: %w", err)
   103  	}
   104  
   105  	return result, nil
   106  }