gitee.com/larksuite/oapi-sdk-go/v3@v3.0.3/core/reqtranslator.go (about) 1 /* 2 * MIT License 3 * 4 * Copyright (c) 2022 Lark Technologies Pte. Ltd. 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 * 8 * The above copyright notice and this permission notice, shall be included in all copies or substantial portions of the Software. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 */ 12 13 package larkcore 14 15 import ( 16 "bytes" 17 "context" 18 "encoding/json" 19 "errors" 20 "fmt" 21 "io" 22 "mime/multipart" 23 "net/http" 24 "net/url" 25 "reflect" 26 "strings" 27 ) 28 29 type ReqTranslator struct { 30 } 31 32 func (translator *ReqTranslator) translate(ctx context.Context, req *ApiReq, accessTokenType AccessTokenType, config *Config, option *RequestOption) (*http.Request, error) { 33 body := req.Body 34 if _, ok := body.(*Formdata); !ok { 35 if option.FileUpload { 36 body = toFormdata(body) 37 } 38 } else { 39 option.FileUpload = true 40 } 41 42 contentType, rawBody, err := translator.payload(body) 43 if err != nil { 44 return nil, err 45 } 46 47 // path 48 var pathSegs []string 49 for _, p := range strings.Split(req.ApiPath, "/") { 50 if strings.Index(p, ":") == 0 { 51 varName := p[1:] 52 v, ok := req.PathParams[varName] 53 if !ok { 54 return nil, fmt.Errorf("http path:%s, name: %s, not found value", req.ApiPath, varName) 55 } 56 val := fmt.Sprint(v) 57 if val == "" { 58 return nil, fmt.Errorf("http path:%s, name: %s, value is empty", req.ApiPath, varName) 59 } 60 val = url.PathEscape(val) 61 pathSegs = append(pathSegs, val) 62 continue 63 } 64 pathSegs = append(pathSegs, p) 65 } 66 newPath := strings.Join(pathSegs, "/") 67 if strings.Index(newPath, "http") != 0 { 68 newPath = fmt.Sprintf("%s%s", config.BaseUrl, newPath) 69 } 70 71 queryPath := req.QueryParams.Encode() 72 if queryPath != "" { 73 newPath = fmt.Sprintf("%s?%s", newPath, queryPath) 74 } 75 76 req1, err := translator.newHTTPRequest(ctx, req.HttpMethod, newPath, contentType, rawBody, accessTokenType, option, config) 77 if err != nil { 78 return nil, err 79 } 80 return req1, nil 81 } 82 83 func (translator *ReqTranslator) translateOld(ctx context.Context, input interface{}, tokenType AccessTokenType, config *Config, httpMethod, httpPath string, option *RequestOption) (*http.Request, error) { 84 paths, queries, body := translator.parseInput(input, option) 85 if _, ok := body.(*Formdata); ok { 86 option.FileUpload = true 87 } 88 89 contentType, rawBody, err := translator.payload(body) 90 if err != nil { 91 return nil, err 92 } 93 94 fullURL, err := translator.getFullReqUrl(config.BaseUrl, httpPath, paths, queries) 95 if err != nil { 96 return nil, err 97 } 98 99 req, err := translator.newHTTPRequest(ctx, httpMethod, fullURL, contentType, rawBody, tokenType, option, config) 100 if err != nil { 101 return nil, err 102 } 103 return req, nil 104 } 105 106 func authorizationToHeader(req *http.Request, token string) { 107 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) 108 } 109 110 func (translator *ReqTranslator) newHTTPRequest(ctx context.Context, 111 httpMethod, url, contentType string, body []byte, 112 accessTokenType AccessTokenType, option *RequestOption, config *Config) (*http.Request, error) { 113 httpRequest, err := http.NewRequestWithContext(ctx, httpMethod, url, bytes.NewBuffer(body)) 114 if err != nil { 115 return nil, err 116 } 117 if option.RequestId != "" { 118 httpRequest.Header.Add(customRequestId, option.RequestId) 119 } 120 for k, vs := range option.Header { 121 for _, v := range vs { 122 httpRequest.Header.Add(k, v) 123 } 124 } 125 httpRequest.Header.Set(userAgentHeader, userAgent()) 126 if contentType != "" { 127 httpRequest.Header.Set(contentTypeHeader, contentType) 128 } 129 switch accessTokenType { 130 case AccessTokenTypeApp: 131 appAccessToken := option.AppAccessToken 132 if config.EnableTokenCache && appAccessToken == "" { 133 appAccessToken, err = tokenManager.getAppAccessToken(ctx, config, option.AppTicket) 134 if err != nil { 135 return nil, err 136 } 137 } 138 authorizationToHeader(httpRequest, appAccessToken) 139 140 case AccessTokenTypeTenant: 141 tenantAccessToken := option.TenantAccessToken 142 if config.EnableTokenCache { 143 tenantAccessToken, err = tokenManager.getTenantAccessToken(ctx, config, option.TenantKey, option.AppTicket) 144 if err != nil { 145 return nil, err 146 } 147 } 148 authorizationToHeader(httpRequest, tenantAccessToken) 149 150 case AccessTokenTypeUser: 151 authorizationToHeader(httpRequest, option.UserAccessToken) 152 } 153 154 if err != nil { 155 return nil, err 156 } 157 158 err = translator.signHelpdeskAuthToken(httpRequest, option.NeedHelpDeskAuth, config.HelpdeskAuthToken) 159 if err != nil { 160 return nil, err 161 } 162 return httpRequest, nil 163 } 164 165 func (translator *ReqTranslator) signHelpdeskAuthToken(rawRequest *http.Request, needHelpDeskAuth bool, authToken string) error { 166 if needHelpDeskAuth { 167 if authToken == "" { 168 return errors.New("help desk API, please set the helpdesk information of lark.App") 169 } 170 rawRequest.Header.Set("X-Lark-Helpdesk-Authorization", authToken) 171 } 172 return nil 173 } 174 175 func (translator *ReqTranslator) getFullReqUrl(domain string, httpPath string, pathVars, queries map[string]interface{}) (string, error) { 176 // path 177 var pathSegs []string 178 for _, p := range strings.Split(httpPath, "/") { 179 if strings.Index(p, ":") == 0 { 180 varName := p[1:] 181 v, ok := pathVars[varName] 182 if !ok { 183 return "", fmt.Errorf("http path:%s, name: %s, not found value", httpPath, varName) 184 } 185 val := fmt.Sprint(v) 186 if val == "" { 187 return "", fmt.Errorf("http path:%s, name: %s, value is empty", httpPath, varName) 188 } 189 val = url.PathEscape(val) 190 pathSegs = append(pathSegs, val) 191 continue 192 } 193 pathSegs = append(pathSegs, p) 194 } 195 newPath := strings.Join(pathSegs, "/") 196 if strings.Index(newPath, "http") != 0 { 197 newPath = fmt.Sprintf("%s%s", domain, newPath) 198 } 199 // query 200 query := make(url.Values) 201 for k, v := range queries { 202 sv := reflect.ValueOf(v) 203 if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { 204 for i := 0; i < sv.Len(); i++ { 205 query.Add(k, fmt.Sprint(sv.Index(i))) 206 } 207 } else { 208 query.Set(k, fmt.Sprint(v)) 209 } 210 } 211 if len(query) > 0 { 212 newPath = fmt.Sprintf("%s?%s", newPath, query.Encode()) 213 } 214 return newPath, nil 215 } 216 217 func (translator *ReqTranslator) payload(body interface{}) (string, []byte, error) { 218 if fd, ok := body.(*Formdata); ok { 219 return fd.content() 220 } 221 contentType := defaultContentType 222 if body == nil { 223 return contentType, nil, nil 224 } 225 bs, err := json.Marshal(body) 226 return contentType, bs, err 227 } 228 229 func NewFormdata() *Formdata { 230 return &Formdata{} 231 } 232 233 func (fd *Formdata) AddField(field string, val interface{}) *Formdata { 234 if fd.fields == nil { 235 fd.fields = map[string]interface{}{} 236 } 237 fd.fields[field] = val 238 return fd 239 } 240 241 func (fd *Formdata) AddFile(field string, r io.Reader) *Formdata { 242 return fd.AddField(field, r) 243 } 244 245 func (fd *Formdata) content() (string, []byte, error) { 246 if fd.data != nil { 247 return fd.data.contentType, fd.data.content, nil 248 } 249 buf := &bytes.Buffer{} 250 writer := multipart.NewWriter(buf) 251 for key, val := range fd.fields { 252 if r, ok := val.(io.Reader); ok { 253 part, err := writer.CreateFormFile(key, "unknown-file") 254 if err != nil { 255 return "", nil, err 256 } 257 _, err = io.Copy(part, r) 258 if err != nil { 259 return "", nil, err 260 } 261 continue 262 } 263 err := writer.WriteField(key, fmt.Sprint(val)) 264 if err != nil { 265 return "", nil, err 266 } 267 } 268 contentType := writer.FormDataContentType() 269 err := writer.Close() 270 if err != nil { 271 return "", nil, err 272 } 273 fd.data = &struct { 274 content []byte 275 contentType string 276 }{content: buf.Bytes(), contentType: contentType} 277 return fd.data.contentType, fd.data.content, nil 278 } 279 280 type Formdata struct { 281 fields map[string]interface{} 282 data *struct { 283 content []byte 284 contentType string 285 } 286 } 287 288 func (translator *ReqTranslator) parseInput(input interface{}, option *RequestOption) (map[string]interface{}, map[string]interface{}, interface{}) { 289 if input == nil { 290 return nil, nil, nil 291 } 292 if _, ok := input.(*Formdata); ok { 293 return nil, nil, input 294 } 295 var hasHTTPTag bool 296 paths, queries := map[string]interface{}{}, map[string]interface{}{} 297 vv := reflect.ValueOf(input) 298 vt := reflect.TypeOf(input) 299 if vt.Kind() == reflect.Ptr { 300 vv = vv.Elem() 301 vt = vt.Elem() 302 } 303 if vt.Kind() != reflect.Struct { 304 return nil, nil, input 305 } 306 var body interface{} 307 for i := 0; i < vt.NumField(); i++ { 308 fieldValue := vv.Field(i) 309 fieldType := vt.Field(i) 310 if path, ok := fieldType.Tag.Lookup("path"); ok { 311 hasHTTPTag = true 312 if path != "" && !isEmptyValue(fieldValue) { 313 paths[path] = reflect.Indirect(fieldValue).Interface() 314 } 315 continue 316 } 317 if query, ok := fieldType.Tag.Lookup("query"); ok { 318 hasHTTPTag = true 319 if query != "" && !isEmptyValue(fieldValue) { 320 queries[query] = reflect.Indirect(fieldValue).Interface() 321 } 322 continue 323 } 324 if _, ok := fieldType.Tag.Lookup("body"); ok { 325 hasHTTPTag = true 326 body = fieldValue.Interface() 327 } 328 } 329 if !hasHTTPTag { 330 body = input 331 if option.FileUpload { 332 body = toFormdata(input) 333 } 334 return nil, nil, body 335 } 336 if body != nil { 337 if option.FileUpload { 338 body = toFormdata(body) 339 } 340 } 341 return paths, queries, body 342 } 343 344 func toFormdata(body interface{}) *Formdata { 345 formdata := &Formdata{} 346 v := reflect.ValueOf(body) 347 t := reflect.TypeOf(body) 348 if t.Kind() == reflect.Ptr { 349 v = v.Elem() 350 t = t.Elem() 351 } 352 for i := 0; i < t.NumField(); i++ { 353 fieldValue := v.Field(i) 354 fieldType := t.Field(i) 355 if isEmptyValue(fieldValue) { 356 continue 357 } 358 if fieldName := fieldType.Tag.Get("json"); fieldName != "" { 359 fieldName = strings.TrimSuffix(fieldName, ",omitempty") 360 formdata.AddField(fieldName, reflect.Indirect(fieldValue).Interface()) 361 } 362 } 363 return formdata 364 }