github.com/Finnhub-Stock-API/finnhub-go@v1.2.1/client.go (about) 1 /* 2 * Finnhub API 3 * 4 * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) 5 * 6 * API version: 1.0.0 7 * Generated by: OpenAPI Generator (https://openapi-generator.tech) 8 */ 9 10 package finnhub 11 12 import ( 13 "bytes" 14 "context" 15 "encoding/json" 16 "encoding/xml" 17 "errors" 18 "fmt" 19 "io" 20 "io/ioutil" 21 "log" 22 "mime/multipart" 23 "net/http" 24 "net/http/httputil" 25 "net/url" 26 "os" 27 "path/filepath" 28 "reflect" 29 "regexp" 30 "strconv" 31 "strings" 32 "time" 33 "unicode/utf8" 34 35 "golang.org/x/oauth2" 36 ) 37 38 var ( 39 jsonCheck = regexp.MustCompile(`(?i:(?:application|text)/(?:vnd\.[^;]+\+)?json)`) 40 xmlCheck = regexp.MustCompile(`(?i:(?:application|text)/xml)`) 41 ) 42 43 // APIClient manages communication with the Finnhub API API v1.0.0 44 // In most cases there should be only one, shared, APIClient. 45 type APIClient struct { 46 cfg *Configuration 47 common service // Reuse a single struct instead of allocating one for each service on the heap. 48 49 // API Services 50 51 DefaultApi *DefaultApiService 52 } 53 54 type service struct { 55 client *APIClient 56 } 57 58 // NewAPIClient creates a new API client. Requires a userAgent string describing your application. 59 // optionally a custom http.Client to allow for advanced features such as caching. 60 func NewAPIClient(cfg *Configuration) *APIClient { 61 if cfg.HTTPClient == nil { 62 cfg.HTTPClient = http.DefaultClient 63 } 64 65 c := &APIClient{} 66 c.cfg = cfg 67 c.common.client = c 68 69 // API Services 70 c.DefaultApi = (*DefaultApiService)(&c.common) 71 72 return c 73 } 74 75 func atoi(in string) (int, error) { 76 return strconv.Atoi(in) 77 } 78 79 // selectHeaderContentType select a content type from the available list. 80 func selectHeaderContentType(contentTypes []string) string { 81 if len(contentTypes) == 0 { 82 return "" 83 } 84 if contains(contentTypes, "application/json") { 85 return "application/json" 86 } 87 return contentTypes[0] // use the first content type specified in 'consumes' 88 } 89 90 // selectHeaderAccept join all accept types and return 91 func selectHeaderAccept(accepts []string) string { 92 if len(accepts) == 0 { 93 return "" 94 } 95 96 if contains(accepts, "application/json") { 97 return "application/json" 98 } 99 100 return strings.Join(accepts, ",") 101 } 102 103 // contains is a case insenstive match, finding needle in a haystack 104 func contains(haystack []string, needle string) bool { 105 for _, a := range haystack { 106 if strings.ToLower(a) == strings.ToLower(needle) { 107 return true 108 } 109 } 110 return false 111 } 112 113 // Verify optional parameters are of the correct type. 114 func typeCheckParameter(obj interface{}, expected string, name string) error { 115 // Make sure there is an object. 116 if obj == nil { 117 return nil 118 } 119 120 // Check the type is as expected. 121 if reflect.TypeOf(obj).String() != expected { 122 return fmt.Errorf("Expected %s to be of type %s but received %s.", name, expected, reflect.TypeOf(obj).String()) 123 } 124 return nil 125 } 126 127 // parameterToString convert interface{} parameters to string, using a delimiter if format is provided. 128 func parameterToString(obj interface{}, collectionFormat string) string { 129 var delimiter string 130 131 switch collectionFormat { 132 case "pipes": 133 delimiter = "|" 134 case "ssv": 135 delimiter = " " 136 case "tsv": 137 delimiter = "\t" 138 case "csv": 139 delimiter = "," 140 } 141 142 if reflect.TypeOf(obj).Kind() == reflect.Slice { 143 return strings.Trim(strings.Replace(fmt.Sprint(obj), " ", delimiter, -1), "[]") 144 } else if t, ok := obj.(time.Time); ok { 145 return t.Format(time.RFC3339) 146 } 147 148 return fmt.Sprintf("%v", obj) 149 } 150 151 // helper for converting interface{} parameters to json strings 152 func parameterToJson(obj interface{}) (string, error) { 153 jsonBuf, err := json.Marshal(obj) 154 if err != nil { 155 return "", err 156 } 157 return string(jsonBuf), err 158 } 159 160 161 // callAPI do the request. 162 func (c *APIClient) callAPI(request *http.Request) (*http.Response, error) { 163 if c.cfg.Debug { 164 dump, err := httputil.DumpRequestOut(request, true) 165 if err != nil { 166 return nil, err 167 } 168 log.Printf("\n%s\n", string(dump)) 169 } 170 171 resp, err := c.cfg.HTTPClient.Do(request) 172 if err != nil { 173 return resp, err 174 } 175 176 if c.cfg.Debug { 177 dump, err := httputil.DumpResponse(resp, true) 178 if err != nil { 179 return resp, err 180 } 181 log.Printf("\n%s\n", string(dump)) 182 } 183 184 return resp, err 185 } 186 187 // ChangeBasePath changes base path to allow switching to mocks 188 func (c *APIClient) ChangeBasePath(path string) { 189 c.cfg.BasePath = path 190 } 191 192 // Allow modification of underlying config for alternate implementations and testing 193 // Caution: modifying the configuration while live can cause data races and potentially unwanted behavior 194 func (c *APIClient) GetConfig() *Configuration { 195 return c.cfg 196 } 197 198 // prepareRequest build the request 199 func (c *APIClient) prepareRequest( 200 ctx context.Context, 201 path string, method string, 202 postBody interface{}, 203 headerParams map[string]string, 204 queryParams url.Values, 205 formParams url.Values, 206 formFileName string, 207 fileName string, 208 fileBytes []byte) (localVarRequest *http.Request, err error) { 209 210 var body *bytes.Buffer 211 212 // Detect postBody type and post. 213 if postBody != nil { 214 contentType := headerParams["Content-Type"] 215 if contentType == "" { 216 contentType = detectContentType(postBody) 217 headerParams["Content-Type"] = contentType 218 } 219 220 body, err = setBody(postBody, contentType) 221 if err != nil { 222 return nil, err 223 } 224 } 225 226 // add form parameters and file if available. 227 if strings.HasPrefix(headerParams["Content-Type"], "multipart/form-data") && len(formParams) > 0 || (len(fileBytes) > 0 && fileName != "") { 228 if body != nil { 229 return nil, errors.New("Cannot specify postBody and multipart form at the same time.") 230 } 231 body = &bytes.Buffer{} 232 w := multipart.NewWriter(body) 233 234 for k, v := range formParams { 235 for _, iv := range v { 236 if strings.HasPrefix(k, "@") { // file 237 err = addFile(w, k[1:], iv) 238 if err != nil { 239 return nil, err 240 } 241 } else { // form value 242 w.WriteField(k, iv) 243 } 244 } 245 } 246 if len(fileBytes) > 0 && fileName != "" { 247 w.Boundary() 248 //_, fileNm := filepath.Split(fileName) 249 part, err := w.CreateFormFile(formFileName, filepath.Base(fileName)) 250 if err != nil { 251 return nil, err 252 } 253 _, err = part.Write(fileBytes) 254 if err != nil { 255 return nil, err 256 } 257 } 258 259 // Set the Boundary in the Content-Type 260 headerParams["Content-Type"] = w.FormDataContentType() 261 262 // Set Content-Length 263 headerParams["Content-Length"] = fmt.Sprintf("%d", body.Len()) 264 w.Close() 265 } 266 267 if strings.HasPrefix(headerParams["Content-Type"], "application/x-www-form-urlencoded") && len(formParams) > 0 { 268 if body != nil { 269 return nil, errors.New("Cannot specify postBody and x-www-form-urlencoded form at the same time.") 270 } 271 body = &bytes.Buffer{} 272 body.WriteString(formParams.Encode()) 273 // Set Content-Length 274 headerParams["Content-Length"] = fmt.Sprintf("%d", body.Len()) 275 } 276 277 // Setup path and query parameters 278 url, err := url.Parse(path) 279 if err != nil { 280 return nil, err 281 } 282 283 // Override request host, if applicable 284 if c.cfg.Host != "" { 285 url.Host = c.cfg.Host 286 } 287 288 // Override request scheme, if applicable 289 if c.cfg.Scheme != "" { 290 url.Scheme = c.cfg.Scheme 291 } 292 293 // Adding Query Param 294 query := url.Query() 295 for k, v := range queryParams { 296 for _, iv := range v { 297 query.Add(k, iv) 298 } 299 } 300 301 // Encode the parameters. 302 url.RawQuery = query.Encode() 303 304 // Generate a new request 305 if body != nil { 306 localVarRequest, err = http.NewRequest(method, url.String(), body) 307 } else { 308 localVarRequest, err = http.NewRequest(method, url.String(), nil) 309 } 310 if err != nil { 311 return nil, err 312 } 313 314 // add header parameters, if any 315 if len(headerParams) > 0 { 316 headers := http.Header{} 317 for h, v := range headerParams { 318 headers.Set(h, v) 319 } 320 localVarRequest.Header = headers 321 } 322 323 // Add the user agent to the request. 324 localVarRequest.Header.Add("User-Agent", c.cfg.UserAgent) 325 326 if ctx != nil { 327 // add context to the request 328 localVarRequest = localVarRequest.WithContext(ctx) 329 330 // Walk through any authentication. 331 332 // OAuth2 authentication 333 if tok, ok := ctx.Value(ContextOAuth2).(oauth2.TokenSource); ok { 334 // We were able to grab an oauth2 token from the context 335 var latestToken *oauth2.Token 336 if latestToken, err = tok.Token(); err != nil { 337 return nil, err 338 } 339 340 latestToken.SetAuthHeader(localVarRequest) 341 } 342 343 // Basic HTTP Authentication 344 if auth, ok := ctx.Value(ContextBasicAuth).(BasicAuth); ok { 345 localVarRequest.SetBasicAuth(auth.UserName, auth.Password) 346 } 347 348 // AccessToken Authentication 349 if auth, ok := ctx.Value(ContextAccessToken).(string); ok { 350 localVarRequest.Header.Add("Authorization", "Bearer "+auth) 351 } 352 353 } 354 355 for header, value := range c.cfg.DefaultHeader { 356 localVarRequest.Header.Add(header, value) 357 } 358 359 return localVarRequest, nil 360 } 361 362 func (c *APIClient) decode(v interface{}, b []byte, contentType string) (err error) { 363 if len(b) == 0 { 364 return nil 365 } 366 if s, ok := v.(*string); ok { 367 *s = string(b) 368 return nil 369 } 370 if f, ok := v.(**os.File); ok { 371 *f, err = ioutil.TempFile("", "HttpClientFile") 372 if err != nil { 373 return 374 } 375 _, err = (*f).Write(b) 376 _, err = (*f).Seek(0, io.SeekStart) 377 return 378 } 379 if xmlCheck.MatchString(contentType) { 380 if err = xml.Unmarshal(b, v); err != nil { 381 return err 382 } 383 return nil 384 } 385 if jsonCheck.MatchString(contentType) { 386 if err = json.Unmarshal(b, v); err != nil { 387 return err 388 } 389 return nil 390 } 391 return errors.New("undefined response type") 392 } 393 394 // Add a file to the multipart request 395 func addFile(w *multipart.Writer, fieldName, path string) error { 396 file, err := os.Open(path) 397 if err != nil { 398 return err 399 } 400 defer file.Close() 401 402 part, err := w.CreateFormFile(fieldName, filepath.Base(path)) 403 if err != nil { 404 return err 405 } 406 _, err = io.Copy(part, file) 407 408 return err 409 } 410 411 // Prevent trying to import "fmt" 412 func reportError(format string, a ...interface{}) error { 413 return fmt.Errorf(format, a...) 414 } 415 416 // Set request body from an interface{} 417 func setBody(body interface{}, contentType string) (bodyBuf *bytes.Buffer, err error) { 418 if bodyBuf == nil { 419 bodyBuf = &bytes.Buffer{} 420 } 421 422 if reader, ok := body.(io.Reader); ok { 423 _, err = bodyBuf.ReadFrom(reader) 424 } else if b, ok := body.([]byte); ok { 425 _, err = bodyBuf.Write(b) 426 } else if s, ok := body.(string); ok { 427 _, err = bodyBuf.WriteString(s) 428 } else if s, ok := body.(*string); ok { 429 _, err = bodyBuf.WriteString(*s) 430 } else if jsonCheck.MatchString(contentType) { 431 err = json.NewEncoder(bodyBuf).Encode(body) 432 } else if xmlCheck.MatchString(contentType) { 433 err = xml.NewEncoder(bodyBuf).Encode(body) 434 } 435 436 if err != nil { 437 return nil, err 438 } 439 440 if bodyBuf.Len() == 0 { 441 err = fmt.Errorf("Invalid body type %s\n", contentType) 442 return nil, err 443 } 444 return bodyBuf, nil 445 } 446 447 // detectContentType method is used to figure out `Request.Body` content type for request header 448 func detectContentType(body interface{}) string { 449 contentType := "text/plain; charset=utf-8" 450 kind := reflect.TypeOf(body).Kind() 451 452 switch kind { 453 case reflect.Struct, reflect.Map, reflect.Ptr: 454 contentType = "application/json; charset=utf-8" 455 case reflect.String: 456 contentType = "text/plain; charset=utf-8" 457 default: 458 if b, ok := body.([]byte); ok { 459 contentType = http.DetectContentType(b) 460 } else if kind == reflect.Slice { 461 contentType = "application/json; charset=utf-8" 462 } 463 } 464 465 return contentType 466 } 467 468 // Ripped from https://github.com/gregjones/httpcache/blob/master/httpcache.go 469 type cacheControl map[string]string 470 471 func parseCacheControl(headers http.Header) cacheControl { 472 cc := cacheControl{} 473 ccHeader := headers.Get("Cache-Control") 474 for _, part := range strings.Split(ccHeader, ",") { 475 part = strings.Trim(part, " ") 476 if part == "" { 477 continue 478 } 479 if strings.ContainsRune(part, '=') { 480 keyval := strings.Split(part, "=") 481 cc[strings.Trim(keyval[0], " ")] = strings.Trim(keyval[1], ",") 482 } else { 483 cc[part] = "" 484 } 485 } 486 return cc 487 } 488 489 // CacheExpires helper function to determine remaining time before repeating a request. 490 func CacheExpires(r *http.Response) time.Time { 491 // Figure out when the cache expires. 492 var expires time.Time 493 now, err := time.Parse(time.RFC1123, r.Header.Get("date")) 494 if err != nil { 495 return time.Now() 496 } 497 respCacheControl := parseCacheControl(r.Header) 498 499 if maxAge, ok := respCacheControl["max-age"]; ok { 500 lifetime, err := time.ParseDuration(maxAge + "s") 501 if err != nil { 502 expires = now 503 } else { 504 expires = now.Add(lifetime) 505 } 506 } else { 507 expiresHeader := r.Header.Get("Expires") 508 if expiresHeader != "" { 509 expires, err = time.Parse(time.RFC1123, expiresHeader) 510 if err != nil { 511 expires = now 512 } 513 } 514 } 515 return expires 516 } 517 518 func strlen(s string) int { 519 return utf8.RuneCountInString(s) 520 } 521 522 // GenericOpenAPIError Provides access to the body, error and model on returned errors. 523 type GenericOpenAPIError struct { 524 body []byte 525 error string 526 model interface{} 527 } 528 529 // Error returns non-empty string if there was an error. 530 func (e GenericOpenAPIError) Error() string { 531 return e.error 532 } 533 534 // Body returns the raw bytes of the response 535 func (e GenericOpenAPIError) Body() []byte { 536 return e.body 537 } 538 539 // Model returns the unpacked model of the error 540 func (e GenericOpenAPIError) Model() interface{} { 541 return e.model 542 }