util.go raw

   1  // Copyright (c) 2015-2024 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
   2  // resty source code and usage is governed by a MIT style
   3  // license that can be found in the LICENSE file.
   4  
   5  package resty
   6  
   7  import (
   8  	"bytes"
   9  	"errors"
  10  	"fmt"
  11  	"io"
  12  	"log"
  13  	"mime/multipart"
  14  	"net/http"
  15  	"net/textproto"
  16  	"os"
  17  	"path/filepath"
  18  	"reflect"
  19  	"runtime"
  20  	"sort"
  21  	"strings"
  22  )
  23  
  24  //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
  25  // Logger interface
  26  //_______________________________________________________________________
  27  
  28  // Logger interface is to abstract the logging from Resty. Gives control to
  29  // the Resty users, choice of the logger.
  30  type Logger interface {
  31  	Errorf(format string, v ...interface{})
  32  	Warnf(format string, v ...interface{})
  33  	Debugf(format string, v ...interface{})
  34  }
  35  
  36  func createLogger() *logger {
  37  	l := &logger{l: log.New(os.Stderr, "", log.Ldate|log.Lmicroseconds)}
  38  	return l
  39  }
  40  
  41  var _ Logger = (*logger)(nil)
  42  
  43  type logger struct {
  44  	l *log.Logger
  45  }
  46  
  47  func (l *logger) Errorf(format string, v ...interface{}) {
  48  	l.output("ERROR RESTY "+format, v...)
  49  }
  50  
  51  func (l *logger) Warnf(format string, v ...interface{}) {
  52  	l.output("WARN RESTY "+format, v...)
  53  }
  54  
  55  func (l *logger) Debugf(format string, v ...interface{}) {
  56  	l.output("DEBUG RESTY "+format, v...)
  57  }
  58  
  59  func (l *logger) output(format string, v ...interface{}) {
  60  	if len(v) == 0 {
  61  		l.l.Print(format)
  62  		return
  63  	}
  64  	l.l.Printf(format, v...)
  65  }
  66  
  67  //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
  68  // Rate Limiter interface
  69  //_______________________________________________________________________
  70  
  71  type RateLimiter interface {
  72  	Allow() bool
  73  }
  74  
  75  var ErrRateLimitExceeded = errors.New("rate limit exceeded")
  76  
  77  //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
  78  // Package Helper methods
  79  //_______________________________________________________________________
  80  
  81  // IsStringEmpty method tells whether given string is empty or not
  82  func IsStringEmpty(str string) bool {
  83  	return len(strings.TrimSpace(str)) == 0
  84  }
  85  
  86  // DetectContentType method is used to figure out `Request.Body` content type for request header
  87  func DetectContentType(body interface{}) string {
  88  	contentType := plainTextType
  89  	kind := kindOf(body)
  90  	switch kind {
  91  	case reflect.Struct, reflect.Map:
  92  		contentType = jsonContentType
  93  	case reflect.String:
  94  		contentType = plainTextType
  95  	default:
  96  		if b, ok := body.([]byte); ok {
  97  			contentType = http.DetectContentType(b)
  98  		} else if kind == reflect.Slice {
  99  			contentType = jsonContentType
 100  		}
 101  	}
 102  
 103  	return contentType
 104  }
 105  
 106  // IsJSONType method is to check JSON content type or not
 107  func IsJSONType(ct string) bool {
 108  	return jsonCheck.MatchString(ct)
 109  }
 110  
 111  // IsXMLType method is to check XML content type or not
 112  func IsXMLType(ct string) bool {
 113  	return xmlCheck.MatchString(ct)
 114  }
 115  
 116  // Unmarshalc content into object from JSON or XML
 117  func Unmarshalc(c *Client, ct string, b []byte, d interface{}) (err error) {
 118  	if IsJSONType(ct) {
 119  		err = c.JSONUnmarshal(b, d)
 120  	} else if IsXMLType(ct) {
 121  		err = c.XMLUnmarshal(b, d)
 122  	}
 123  
 124  	return
 125  }
 126  
 127  //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
 128  // RequestLog and ResponseLog type
 129  //_______________________________________________________________________
 130  
 131  // RequestLog struct is used to collected information from resty request
 132  // instance for debug logging. It sent to request log callback before resty
 133  // actually logs the information.
 134  type RequestLog struct {
 135  	Header http.Header
 136  	Body   string
 137  }
 138  
 139  // ResponseLog struct is used to collected information from resty response
 140  // instance for debug logging. It sent to response log callback before resty
 141  // actually logs the information.
 142  type ResponseLog struct {
 143  	Header http.Header
 144  	Body   string
 145  }
 146  
 147  // way to disable the HTML escape as opt-in
 148  func jsonMarshal(c *Client, r *Request, d interface{}) (*bytes.Buffer, error) {
 149  	if !r.jsonEscapeHTML || !c.jsonEscapeHTML {
 150  		return noescapeJSONMarshal(d)
 151  	}
 152  
 153  	data, err := c.JSONMarshal(d)
 154  	if err != nil {
 155  		return nil, err
 156  	}
 157  
 158  	buf := acquireBuffer()
 159  	_, _ = buf.Write(data)
 160  	return buf, nil
 161  }
 162  
 163  func firstNonEmpty(v ...string) string {
 164  	for _, s := range v {
 165  		if !IsStringEmpty(s) {
 166  			return s
 167  		}
 168  	}
 169  	return ""
 170  }
 171  
 172  var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
 173  
 174  func escapeQuotes(s string) string {
 175  	return quoteEscaper.Replace(s)
 176  }
 177  
 178  func createMultipartHeader(param, fileName, contentType string) textproto.MIMEHeader {
 179  	hdr := make(textproto.MIMEHeader)
 180  
 181  	var contentDispositionValue string
 182  	if IsStringEmpty(fileName) {
 183  		contentDispositionValue = fmt.Sprintf(`form-data; name="%s"`, param)
 184  	} else {
 185  		contentDispositionValue = fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
 186  			param, escapeQuotes(fileName))
 187  	}
 188  	hdr.Set("Content-Disposition", contentDispositionValue)
 189  
 190  	if !IsStringEmpty(contentType) {
 191  		hdr.Set(hdrContentTypeKey, contentType)
 192  	}
 193  	return hdr
 194  }
 195  
 196  func addMultipartFormField(w *multipart.Writer, mf *MultipartField) error {
 197  	partWriter, err := w.CreatePart(createMultipartHeader(mf.Param, mf.FileName, mf.ContentType))
 198  	if err != nil {
 199  		return err
 200  	}
 201  
 202  	_, err = io.Copy(partWriter, mf.Reader)
 203  	return err
 204  }
 205  
 206  func writeMultipartFormFile(w *multipart.Writer, fieldName, fileName string, r io.Reader) error {
 207  	// Auto detect actual multipart content type
 208  	cbuf := make([]byte, 512)
 209  	size, err := r.Read(cbuf)
 210  	if err != nil && err != io.EOF {
 211  		return err
 212  	}
 213  
 214  	partWriter, err := w.CreatePart(createMultipartHeader(fieldName, fileName, http.DetectContentType(cbuf[:size])))
 215  	if err != nil {
 216  		return err
 217  	}
 218  
 219  	if _, err = partWriter.Write(cbuf[:size]); err != nil {
 220  		return err
 221  	}
 222  
 223  	_, err = io.Copy(partWriter, r)
 224  	return err
 225  }
 226  
 227  func addFile(w *multipart.Writer, fieldName, path string) error {
 228  	file, err := os.Open(path)
 229  	if err != nil {
 230  		return err
 231  	}
 232  	defer closeq(file)
 233  	return writeMultipartFormFile(w, fieldName, filepath.Base(path), file)
 234  }
 235  
 236  func addFileReader(w *multipart.Writer, f *File) error {
 237  	return writeMultipartFormFile(w, f.ParamName, f.Name, f.Reader)
 238  }
 239  
 240  func getPointer(v interface{}) interface{} {
 241  	vv := valueOf(v)
 242  	if vv.Kind() == reflect.Ptr {
 243  		return v
 244  	}
 245  	return reflect.New(vv.Type()).Interface()
 246  }
 247  
 248  func isPayloadSupported(m string, allowMethodGet bool) bool {
 249  	return !(m == MethodHead || m == MethodOptions || (m == MethodGet && !allowMethodGet))
 250  }
 251  
 252  func typeOf(i interface{}) reflect.Type {
 253  	return indirect(valueOf(i)).Type()
 254  }
 255  
 256  func valueOf(i interface{}) reflect.Value {
 257  	return reflect.ValueOf(i)
 258  }
 259  
 260  func indirect(v reflect.Value) reflect.Value {
 261  	return reflect.Indirect(v)
 262  }
 263  
 264  func kindOf(v interface{}) reflect.Kind {
 265  	return typeOf(v).Kind()
 266  }
 267  
 268  func createDirectory(dir string) (err error) {
 269  	if _, err = os.Stat(dir); err != nil {
 270  		if os.IsNotExist(err) {
 271  			if err = os.MkdirAll(dir, 0755); err != nil {
 272  				return
 273  			}
 274  		}
 275  	}
 276  	return
 277  }
 278  
 279  func canJSONMarshal(contentType string, kind reflect.Kind) bool {
 280  	return IsJSONType(contentType) && (kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice)
 281  }
 282  
 283  func functionName(i interface{}) string {
 284  	return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
 285  }
 286  
 287  func acquireBuffer() *bytes.Buffer {
 288  	buf := bufPool.Get().(*bytes.Buffer)
 289  	if buf.Len() == 0 {
 290  		buf.Reset()
 291  		return buf
 292  	}
 293  	bufPool.Put(buf)
 294  	return new(bytes.Buffer)
 295  }
 296  
 297  func releaseBuffer(buf *bytes.Buffer) {
 298  	if buf != nil {
 299  		buf.Reset()
 300  		bufPool.Put(buf)
 301  	}
 302  }
 303  
 304  func backToBufPool(buf *bytes.Buffer) {
 305  	if buf != nil {
 306  		bufPool.Put(buf)
 307  	}
 308  }
 309  
 310  func closeq(v interface{}) {
 311  	if c, ok := v.(io.Closer); ok {
 312  		silently(c.Close())
 313  	}
 314  }
 315  
 316  func silently(_ ...interface{}) {}
 317  
 318  func composeHeaders(c *Client, r *Request, hdrs http.Header) string {
 319  	str := make([]string, 0, len(hdrs))
 320  	for _, k := range sortHeaderKeys(hdrs) {
 321  		str = append(str, "\t"+strings.TrimSpace(fmt.Sprintf("%25s: %s", k, strings.Join(hdrs[k], ", "))))
 322  	}
 323  	return strings.Join(str, "\n")
 324  }
 325  
 326  func sortHeaderKeys(hdrs http.Header) []string {
 327  	keys := make([]string, 0, len(hdrs))
 328  	for key := range hdrs {
 329  		keys = append(keys, key)
 330  	}
 331  	sort.Strings(keys)
 332  	return keys
 333  }
 334  
 335  func copyHeaders(hdrs http.Header) http.Header {
 336  	nh := http.Header{}
 337  	for k, v := range hdrs {
 338  		nh[k] = v
 339  	}
 340  	return nh
 341  }
 342  
 343  func wrapErrors(n error, inner error) error {
 344  	if inner == nil {
 345  		return n
 346  	}
 347  	if n == nil {
 348  		return inner
 349  	}
 350  	return &restyError{
 351  		err:   n,
 352  		inner: inner,
 353  	}
 354  }
 355  
 356  type restyError struct {
 357  	err   error
 358  	inner error
 359  }
 360  
 361  func (e *restyError) Error() string {
 362  	return e.err.Error()
 363  }
 364  
 365  func (e *restyError) Unwrap() error {
 366  	return e.inner
 367  }
 368  
 369  type noRetryErr struct {
 370  	err error
 371  }
 372  
 373  func (e *noRetryErr) Error() string {
 374  	return e.err.Error()
 375  }
 376  
 377  func wrapNoRetryErr(err error) error {
 378  	if err != nil {
 379  		err = &noRetryErr{err: err}
 380  	}
 381  	return err
 382  }
 383  
 384  func unwrapNoRetryErr(err error) error {
 385  	if e, ok := err.(*noRetryErr); ok {
 386  		err = e.err
 387  	}
 388  	return err
 389  }
 390