github.com/rohankumardubey/aresdb@v0.0.2-0.20190517170215-e54e3ca06b9c/api/request.go (about)

     1  //  Copyright (c) 2017-2018 Uber Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package api
    16  
    17  import (
    18  	"encoding/json"
    19  	"io/ioutil"
    20  	"net/http"
    21  	"reflect"
    22  	"strconv"
    23  	"strings"
    24  
    25  	"github.com/gorilla/mux"
    26  	"github.com/uber/aresdb/utils"
    27  )
    28  
    29  // ReadRequest reads request.
    30  // obj passed into this method has to be a pointer to a struct of request object
    31  // Each request object will have path params tagged as `path:""` if needed
    32  // and post body tagged as `body:""` if needed
    33  // path tag must have parameter name, which will be used to read path param
    34  // body tag field has to be a struct.
    35  // eg.
    36  //      type AddEnumCaseRequest struct {
    37  //      	TableName string `path:"table"`
    38  //      	ColumnName string `path:"column"`
    39  //      	Body struct {
    40  //      		EnumCase string `json:"enumCase"`
    41  //      	} `body:""`
    42  //      }
    43  func ReadRequest(r *http.Request, obj interface{}) error {
    44  	vValue := reflect.ValueOf(obj)
    45  	vType := reflect.TypeOf(obj)
    46  	if vType.Kind() != reflect.Ptr || vType.Elem().Kind() != reflect.Struct {
    47  		return utils.APIError{
    48  			Code:    http.StatusInternalServerError,
    49  			Message: "Expecting request object to be a pointer to struct",
    50  		}
    51  	}
    52  
    53  	var formParsed bool
    54  	for i := 0; i < vType.Elem().NumField(); i++ {
    55  		var isPathParam, isQueryParam, isHeaderParam, optional bool
    56  		var paramName, paramValue string
    57  
    58  		field := vType.Elem().Field(i)
    59  		valueField := vValue.Elem().Field(i)
    60  		// If it's anonymous field, we apply ReadRequest to this struct directly.
    61  		if field.Type.Kind() == reflect.Struct && field.Anonymous {
    62  			if err := ReadRequest(r, valueField.Addr().Interface()); err != nil {
    63  				return err
    64  			}
    65  		}
    66  
    67  		if paramName, isHeaderParam = field.Tag.Lookup("header"); isHeaderParam {
    68  			tagValues := strings.Split(paramName, ",")
    69  			paramName = tagValues[0]
    70  			if len(tagValues) == 2 && tagValues[1] == "optional" {
    71  				optional = true
    72  			}
    73  			paramValue = r.Header.Get(paramName)
    74  		} else if paramName, isPathParam = field.Tag.Lookup("path"); isPathParam {
    75  			vars := mux.Vars(r)
    76  			if vars == nil {
    77  				return ErrMissingParameter
    78  			}
    79  			paramValue = vars[paramName]
    80  		} else if paramName, isQueryParam = field.Tag.Lookup("query"); isQueryParam {
    81  			tagValues := strings.Split(paramName, ",")
    82  			paramName = tagValues[0]
    83  			if len(tagValues) == 2 && tagValues[1] == "optional" {
    84  				optional = true
    85  			}
    86  			if !formParsed {
    87  				if err := r.ParseForm(); err != nil && !optional {
    88  					return ErrMissingParameter
    89  				}
    90  				formParsed = true
    91  			}
    92  			paramValue = r.Form.Get(paramName)
    93  		}
    94  
    95  		if isPathParam || isQueryParam || isHeaderParam {
    96  			if paramValue == "" {
    97  				if optional {
    98  					continue
    99  				}
   100  				return ErrMissingParameter
   101  			}
   102  			// Only string and int is supported in request path fields.
   103  			switch field.Type.Kind() {
   104  			case reflect.String:
   105  				valueField.SetString(paramValue)
   106  			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   107  				intVal, err := strconv.ParseInt(paramValue, 10, 64)
   108  				if err != nil {
   109  					return ErrMissingParameter
   110  				}
   111  				valueField.SetInt(intVal)
   112  			case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   113  				uintVal, err := strconv.ParseUint(paramValue, 10, 64)
   114  				if err != nil {
   115  					return ErrMissingParameter
   116  				}
   117  				valueField.SetUint(uintVal)
   118  			default:
   119  				return ErrMissingParameter
   120  			}
   121  		} else if _, isPostBody := field.Tag.Lookup("body"); isPostBody {
   122  			requestBody, err := ioutil.ReadAll(r.Body)
   123  			if err != nil {
   124  				return utils.APIError{
   125  					Code:    http.StatusBadRequest,
   126  					Message: ErrMsgFailedToReadRequestBody,
   127  					Cause:   err,
   128  				}
   129  			}
   130  
   131  			switch valueField.Addr().Interface().(type) {
   132  			case *[]byte:
   133  				valueField.SetBytes(requestBody)
   134  			case *json.RawMessage:
   135  				valueField.SetBytes(requestBody)
   136  			default:
   137  				err = json.Unmarshal(requestBody, valueField.Addr().Interface())
   138  				if err != nil {
   139  					return utils.APIError{
   140  						Code:    http.StatusBadRequest,
   141  						Message: ErrMsgFailedToUnmarshalRequest,
   142  						Cause:   err,
   143  					}
   144  				}
   145  			}
   146  		}
   147  	}
   148  	return nil
   149  }
   150  
   151  // ContentType defines the type of http content-type.
   152  type ContentType string
   153  
   154  const (
   155  	// ContentTypeUpsertBatch defines the upsert data content type.
   156  	ContentTypeUpsertBatch = "application/upsert-data"
   157  	// ContentTypeHyperLogLog defines the hyperloglog query result content type.
   158  	ContentTypeHyperLogLog = "application/hll"
   159  	// ContentTypeJSON defines the json content type.
   160  	ContentTypeJSON = "application/json"
   161  )