Skip to content

Commit

Permalink
- Move api_client implementation to a separate file
Browse files Browse the repository at this point in the history
- Add methods for configuring an APIClient
  • Loading branch information
Haven-King committed Sep 30, 2024
1 parent 3ba7570 commit 84c786a
Show file tree
Hide file tree
Showing 2 changed files with 276 additions and 233 deletions.
245 changes: 12 additions & 233 deletions sdk/client/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,14 @@
package client

import (
"bytes"
"compress/gzip"
"context"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io"
"mime/multipart"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"reflect"
"regexp"
"strings"
"time"

"github.com/conductor-sdk/conductor-go/sdk/authentication"
"github.com/conductor-sdk/conductor-go/sdk/settings"
"github.com/sirupsen/logrus"
)

const (
Expand All @@ -46,6 +32,9 @@ var (
)

type APIClient struct {
dialer *net.Dialer
netTransport *http.Transport
httpClient *http.Client
httpRequester *HttpRequester
tokenManager authentication.TokenManager
}
Expand Down Expand Up @@ -94,97 +83,6 @@ func NewAPIClientWithTokenManager(
)
}

func newAPIClient(authenticationSettings *settings.AuthenticationSettings, httpSettings *settings.HttpSettings, tokenExpiration *authentication.TokenExpiration, tokenManager authentication.TokenManager) *APIClient {
if httpSettings == nil {
httpSettings = settings.NewHttpDefaultSettings()
}
baseDialer := &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}
netTransport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: baseDialer.DialContext,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
DisableCompression: false,
}
client := http.Client{
Transport: netTransport,
CheckRedirect: nil,
Jar: nil,
Timeout: 30 * time.Second,
}
return &APIClient{
httpRequester: NewHttpRequester(
authenticationSettings, httpSettings, &client, tokenExpiration, tokenManager,
),
}
}

// callAPI do the request.
func (c *APIClient) callAPI(request *http.Request) (*http.Response, error) {
return c.httpRequester.httpClient.Do(request)
}

func (c *APIClient) decode(v interface{}, b []byte, contentType string) (err error) {
if strings.Contains(contentType, "application/xml") {
if err = xml.Unmarshal(b, v); err != nil {
return err
}
return nil
} else if strings.Contains(contentType, "application/json") {
if err = json.Unmarshal(b, v); err != nil {
return err
}
return nil
} else if strings.Contains(contentType, "text/plain") {
rv := reflect.ValueOf(v)
if rv.IsNil() {
return errors.New("undefined response type")
}
rv.Elem().SetString(string(b))
return nil
}
return errors.New("undefined response type")
}

func (c *APIClient) prepareRequest(
ctx context.Context,
path string, method string,
postBody interface{},
headerParams map[string]string,
queryParams url.Values,
formParams url.Values,
fileName string,
fileBytes []byte,
) (localVarRequest *http.Request, err error) {
return c.httpRequester.prepareRequest(
ctx, path, method, postBody, headerParams, queryParams, formParams, fileName, fileBytes,
)
}

// Ripped from https://github.com/gregjones/httpcache/blob/master/httpcache.go
type cacheControl map[string]string

func parseCacheControl(headers http.Header) cacheControl {
cc := cacheControl{}
ccHeader := headers.Get("Cache-Control")
for _, part := range strings.Split(ccHeader, ",") {
part = strings.Trim(part, " ")
if part == "" {
continue
}
if strings.ContainsRune(part, '=') {
keyval := strings.Split(part, "=")
cc[strings.Trim(keyval[0], " ")] = strings.Trim(keyval[1], ",")
} else {
cc[part] = ""
}
}
return cc
}

// CacheExpires helper function to determine remaining time before repeating a request.
func CacheExpires(r *http.Response) time.Time {
// Figure out when the cache expires.
Expand Down Expand Up @@ -213,139 +111,20 @@ func CacheExpires(r *http.Response) time.Time {
return expires
}

func selectHeaderContentType(contentTypes []string) string {
if len(contentTypes) == 0 {
return ""
}
if contains(contentTypes, "application/json") {
return "application/json"
}
return contentTypes[0] // use the first content type specified in 'consumes'
}
func (client *APIClient) ConfigureDialer(configurer func(dialer *net.Dialer)) *APIClient {
configurer(client.dialer)

// selectHeaderAccept join all accept types and return
func selectHeaderAccept(accepts []string) string {
if len(accepts) == 0 {
return ""
}
if contains(accepts, "application/json") {
return "application/json"
}
return strings.Join(accepts, ",")
return client
}

func contains(haystack []string, needle string) bool {
for _, a := range haystack {
if strings.EqualFold(a, needle) {
return true
}
}
return false
}
func (client *APIClient) ConfigureTransport(configurer func(transport *http.Transport)) *APIClient {
configurer(client.netTransport)

func parameterToString(obj interface{}, collectionFormat string) string {
var delimiter string

switch collectionFormat {
case "pipes":
delimiter = "|"
case "ssv":
delimiter = " "
case "tsv":
delimiter = "\t"
case "csv":
delimiter = ","
}

if reflect.TypeOf(obj).Kind() == reflect.Slice {
return strings.Trim(strings.Replace(fmt.Sprint(obj), " ", delimiter, -1), "[]")
}

return fmt.Sprintf("%v", obj)
}

func setBody(body interface{}, contentType string) (bodyBuf *bytes.Buffer, err error) {
if bodyBuf == nil {
bodyBuf = &bytes.Buffer{}
}
if reader, ok := body.(io.Reader); ok {
_, err = bodyBuf.ReadFrom(reader)
} else if b, ok := body.([]byte); ok {
_, err = bodyBuf.Write(b)
} else if s, ok := body.(string); ok {
_, err = bodyBuf.WriteString(s)
} else if s, ok := body.(*string); ok {
_, err = bodyBuf.WriteString(*s)
} else if jsonCheck.MatchString(contentType) {
err = json.NewEncoder(bodyBuf).Encode(body)
} else if xmlCheck.MatchString(contentType) {
xml.NewEncoder(bodyBuf).Encode(body)
}

if err != nil {
return nil, err
}

if bodyBuf.Len() == 0 {
err = fmt.Errorf("invalid body type %s", contentType)
return nil, err
}
return bodyBuf, nil
return client
}

func detectContentType(body interface{}) string {
contentType := "text/plain; charset=utf-8"
kind := reflect.TypeOf(body).Kind()

switch kind {
case reflect.Struct, reflect.Map, reflect.Ptr:
contentType = "application/json; charset=utf-8"
case reflect.String:
contentType = "text/plain; charset=utf-8"
default:
if b, ok := body.([]byte); ok {
contentType = http.DetectContentType(b)
} else if kind == reflect.Slice {
contentType = "application/json; charset=utf-8"
}
}

return contentType
}

func getDecompressedBody(response *http.Response) ([]byte, error) {
defer response.Body.Close()
var reader io.ReadCloser
var err error
switch response.Header.Get("Content-Encoding") {
case "gzip":
reader, err = gzip.NewReader(response.Body)
if err != nil {
logrus.Error("Unable to decompress the response ", err.Error())
if err == io.EOF {
return nil, nil
}
return nil, err
}
default:
reader = response.Body
}
defer reader.Close()
return io.ReadAll(reader)
}

func addFile(w *multipart.Writer, fieldName, path string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()

part, err := w.CreateFormFile(fieldName, filepath.Base(path))
if err != nil {
return err
}
_, err = io.Copy(part, file)
func (client *APIClient) ConfigureHttpClient(configurer func(client *http.Client)) *APIClient {
configurer(client.httpClient)

return err
return client
}
Loading

0 comments on commit 84c786a

Please sign in to comment.