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 )