github.com/blend/go-sdk@v1.20220411.3/webutil/request_option.go (about)

     1  /*
     2  
     3  Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package webutil
     9  
    10  import (
    11  	"bytes"
    12  	"context"
    13  	"encoding/json"
    14  	"encoding/xml"
    15  	"fmt"
    16  	"io"
    17  	"mime/multipart"
    18  	"net/http"
    19  	"net/textproto"
    20  	"net/url"
    21  	"strings"
    22  )
    23  
    24  // RequestOption is an option for http.Request.
    25  type RequestOption func(*http.Request) error
    26  
    27  // RequestOptions are an array of RequestOption.
    28  type RequestOptions []RequestOption
    29  
    30  // Apply applies the options to a request.
    31  func (ro RequestOptions) Apply(req *http.Request) (err error) {
    32  	for _, option := range ro {
    33  		if err = option(req); err != nil {
    34  			return
    35  		}
    36  	}
    37  	return
    38  }
    39  
    40  // OptMethod sets the request method.
    41  func OptMethod(method string) RequestOption {
    42  	return func(r *http.Request) error {
    43  		r.Method = method
    44  		return nil
    45  	}
    46  }
    47  
    48  // OptGet sets the request method.
    49  func OptGet() RequestOption {
    50  	return func(r *http.Request) error {
    51  		r.Method = "GET"
    52  		return nil
    53  	}
    54  }
    55  
    56  // OptPost sets the request method.
    57  func OptPost() RequestOption {
    58  	return func(r *http.Request) error {
    59  		r.Method = "POST"
    60  		return nil
    61  	}
    62  }
    63  
    64  // OptPut sets the request method.
    65  func OptPut() RequestOption {
    66  	return func(r *http.Request) error {
    67  		r.Method = "PUT"
    68  		return nil
    69  	}
    70  }
    71  
    72  // OptPatch sets the request method.
    73  func OptPatch() RequestOption {
    74  	return func(r *http.Request) error {
    75  		r.Method = "PATCH"
    76  		return nil
    77  	}
    78  }
    79  
    80  // OptDelete sets the request method.
    81  func OptDelete() RequestOption {
    82  	return func(r *http.Request) error {
    83  		r.Method = "DELETE"
    84  		return nil
    85  	}
    86  }
    87  
    88  // OptContext sets the request context.
    89  func OptContext(ctx context.Context) RequestOption {
    90  	return func(r *http.Request) error {
    91  		*r = *r.WithContext(ctx)
    92  		return nil
    93  	}
    94  }
    95  
    96  // OptBasicAuth is an option that sets the http basic auth.
    97  func OptBasicAuth(username, password string) RequestOption {
    98  	return func(r *http.Request) error {
    99  		if r.Header == nil {
   100  			r.Header = http.Header{}
   101  		}
   102  		r.SetBasicAuth(username, password)
   103  		return nil
   104  	}
   105  }
   106  
   107  // OptQuery sets the full querystring.
   108  func OptQuery(query url.Values) RequestOption {
   109  	return func(r *http.Request) error {
   110  		if r.URL == nil {
   111  			r.URL = &url.URL{}
   112  		}
   113  		r.URL.RawQuery = query.Encode()
   114  		return nil
   115  	}
   116  }
   117  
   118  // OptQueryValue sets a query value on a request.
   119  func OptQueryValue(key, value string) RequestOption {
   120  	return func(r *http.Request) error {
   121  		if r.URL == nil {
   122  			r.URL = &url.URL{}
   123  		}
   124  		existing := r.URL.Query()
   125  		existing.Set(key, value)
   126  		r.URL.RawQuery = existing.Encode()
   127  		return nil
   128  	}
   129  }
   130  
   131  // OptQueryValueAdd adds a query value on a request.
   132  func OptQueryValueAdd(key, value string) RequestOption {
   133  	return func(r *http.Request) error {
   134  		if r.URL == nil {
   135  			r.URL = &url.URL{}
   136  		}
   137  		existing := r.URL.Query()
   138  		existing.Add(key, value)
   139  		r.URL.RawQuery = existing.Encode()
   140  		return nil
   141  	}
   142  }
   143  
   144  // OptHeader sets the request headers.
   145  func OptHeader(headers http.Header) RequestOption {
   146  	return func(r *http.Request) error {
   147  		r.Header = headers
   148  		return nil
   149  	}
   150  }
   151  
   152  // OptHeaderValue sets a header value on a request.
   153  func OptHeaderValue(key, value string) RequestOption {
   154  	return func(r *http.Request) error {
   155  		if r.Header == nil {
   156  			r.Header = make(http.Header)
   157  		}
   158  		r.Header.Set(key, value)
   159  		return nil
   160  	}
   161  }
   162  
   163  // OptPostForm sets the request post form and the content type.
   164  func OptPostForm(postForm url.Values) RequestOption {
   165  	return func(r *http.Request) error {
   166  		if r.Header == nil {
   167  			r.Header = http.Header{}
   168  		}
   169  		r.Header.Set(HeaderContentType, ContentTypeApplicationFormEncoded)
   170  		r.PostForm = postForm
   171  		return nil
   172  	}
   173  }
   174  
   175  // OptPostFormValue sets a request post form value.
   176  func OptPostFormValue(key, value string) RequestOption {
   177  	return func(r *http.Request) error {
   178  		if r.Header == nil {
   179  			r.Header = http.Header{}
   180  		}
   181  		r.Header.Set(HeaderContentType, ContentTypeApplicationFormEncoded)
   182  		if r.PostForm == nil {
   183  			r.PostForm = url.Values{}
   184  		}
   185  		r.PostForm.Set(key, value)
   186  		return nil
   187  	}
   188  }
   189  
   190  // OptCookie adds a cookie to a context.
   191  func OptCookie(cookie *http.Cookie) RequestOption {
   192  	return func(r *http.Request) error {
   193  		if r.Header == nil {
   194  			r.Header = make(http.Header)
   195  		}
   196  		r.AddCookie(cookie)
   197  		return nil
   198  	}
   199  }
   200  
   201  // OptCookieValue adds a cookie value to a context.
   202  func OptCookieValue(key, value string) RequestOption {
   203  	return OptCookie(&http.Cookie{Name: key, Value: value})
   204  }
   205  
   206  // OptBody sets the post body on the request.
   207  func OptBody(contents io.ReadCloser) RequestOption {
   208  	return func(r *http.Request) error {
   209  		r.Body = contents
   210  		return nil
   211  	}
   212  }
   213  
   214  // OptBodyBytes sets a body on a context from bytes.
   215  func OptBodyBytes(body []byte) RequestOption {
   216  	return func(r *http.Request) error {
   217  		r.ContentLength = int64(len(body))
   218  		r.Body = io.NopCloser(bytes.NewReader(body))
   219  		r.GetBody = func() (io.ReadCloser, error) {
   220  			return io.NopCloser(bytes.NewReader(body)), nil
   221  		}
   222  		r.ContentLength = int64(len(body))
   223  		return nil
   224  	}
   225  }
   226  
   227  // OptPostedFiles sets a body from posted files.
   228  func OptPostedFiles(files ...PostedFile) RequestOption {
   229  	return func(r *http.Request) error {
   230  		if r.Header == nil {
   231  			r.Header = make(http.Header)
   232  		}
   233  
   234  		b := new(bytes.Buffer)
   235  		w := multipart.NewWriter(b)
   236  
   237  		if len(r.PostForm) > 0 {
   238  			for key, values := range r.PostForm {
   239  				for _, value := range values {
   240  					if err := w.WriteField(key, value); err != nil {
   241  						return err
   242  					}
   243  				}
   244  			}
   245  		}
   246  
   247  		for _, file := range files {
   248  			// custom header since CreateFormFile uses application/octet-stream by default
   249  			var fw io.Writer
   250  			var err error
   251  			if file.ContentType != "" {
   252  				h := make(textproto.MIMEHeader)
   253  				h.Set("Content-Disposition",
   254  					fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
   255  						escapeQuotes(file.Key), escapeQuotes(file.FileName)))
   256  				h.Set("Content-Type", file.ContentType)
   257  				fw, err = w.CreatePart(h)
   258  				if err != nil {
   259  					return err
   260  				}
   261  			} else {
   262  				fw, err = w.CreateFormFile(file.Key, file.FileName)
   263  				if err != nil {
   264  					return err
   265  				}
   266  			}
   267  			_, err = io.Copy(fw, bytes.NewBuffer(file.Contents))
   268  			if err != nil {
   269  				return err
   270  			}
   271  		}
   272  		r.Header.Set(HeaderContentType, w.FormDataContentType())
   273  		if err := w.Close(); err != nil {
   274  			return err
   275  		}
   276  
   277  		bb := b.Bytes()
   278  		r.Body = io.NopCloser(bytes.NewReader(bb))
   279  		r.GetBody = func() (io.ReadCloser, error) {
   280  			return io.NopCloser(bytes.NewReader(bb)), nil
   281  		}
   282  		r.ContentLength = int64(len(bb))
   283  		return nil
   284  	}
   285  }
   286  
   287  // OptJSONBody sets the post body on the request.
   288  func OptJSONBody(obj interface{}) RequestOption {
   289  	return func(r *http.Request) error {
   290  		contents, err := json.Marshal(obj)
   291  		if err != nil {
   292  			return err
   293  		}
   294  		r.Body = io.NopCloser(bytes.NewReader(contents))
   295  		r.GetBody = func() (io.ReadCloser, error) {
   296  			r := bytes.NewReader(contents)
   297  			return io.NopCloser(r), nil
   298  		}
   299  		r.ContentLength = int64(len(contents))
   300  		if r.Header == nil {
   301  			r.Header = make(http.Header)
   302  		}
   303  		r.Header.Set(HeaderContentType, ContentTypeApplicationJSON)
   304  		return nil
   305  	}
   306  }
   307  
   308  // OptXMLBody sets the post body on the request.
   309  func OptXMLBody(obj interface{}) RequestOption {
   310  	return func(r *http.Request) error {
   311  		contents, err := xml.Marshal(obj)
   312  		if err != nil {
   313  			return err
   314  		}
   315  		r.Body = io.NopCloser(bytes.NewBuffer(contents))
   316  		r.GetBody = func() (io.ReadCloser, error) {
   317  			r := bytes.NewReader(contents)
   318  			return io.NopCloser(r), nil
   319  		}
   320  		r.ContentLength = int64(len(contents))
   321  		if r.Header == nil {
   322  			r.Header = make(http.Header)
   323  		}
   324  		r.Header.Set(HeaderContentType, ContentTypeApplicationXML)
   325  		return nil
   326  	}
   327  }
   328  
   329  // OptHTTPClientTrace sets the http trace on the outgoing request.
   330  func OptHTTPClientTrace(ht *HTTPTrace) RequestOption {
   331  	return func(r *http.Request) error {
   332  		*r = *WithClientHTTPTrace(r, ht)
   333  		return nil
   334  	}
   335  }
   336  
   337  var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
   338  
   339  func escapeQuotes(s string) string {
   340  	return quoteEscaper.Replace(s)
   341  }