github.com/phrase/openapi@v0.0.0-20240514140800-49e8a106740e/openapi-generator/templates/go/client.mustache (about)

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