github.com/anthropics/anthropic-sdk-go@v1.70.1/internal/requestconfig/requestconfig.go (about)

     1  // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
     2  
     3  package requestconfig
     4  
     5  import (
     6  	"bytes"
     7  	"context"
     8  	"encoding/json"
     9  	"fmt"
    10  	"io"
    11  	"math"
    12  	"math/rand"
    13  	"mime"
    14  	"net/http"
    15  	"net/url"
    16  	"runtime"
    17  	"strconv"
    18  	"strings"
    19  	"time"
    20  
    21  	"github.com/anthropics/anthropic-sdk-go/internal"
    22  	"github.com/anthropics/anthropic-sdk-go/internal/apierror"
    23  	"github.com/anthropics/anthropic-sdk-go/internal/apiform"
    24  	"github.com/anthropics/anthropic-sdk-go/internal/apiquery"
    25  )
    26  
    27  func getDefaultHeaders() map[string]string {
    28  	return map[string]string{
    29  		"User-Agent": fmt.Sprintf("Anthropic/Go %s", internal.PackageVersion),
    30  	}
    31  }
    32  
    33  func getNormalizedOS() string {
    34  	switch runtime.GOOS {
    35  	case "ios":
    36  		return "iOS"
    37  	case "android":
    38  		return "Android"
    39  	case "darwin":
    40  		return "MacOS"
    41  	case "windows":
    42  		return "Windows"
    43  	case "freebsd":
    44  		return "FreeBSD"
    45  	case "openbsd":
    46  		return "OpenBSD"
    47  	case "linux":
    48  		return "Linux"
    49  	default:
    50  		return fmt.Sprintf("Other:%s", runtime.GOOS)
    51  	}
    52  }
    53  
    54  func getNormalizedArchitecture() string {
    55  	switch runtime.GOARCH {
    56  	case "386":
    57  		return "x32"
    58  	case "amd64":
    59  		return "x64"
    60  	case "arm":
    61  		return "arm"
    62  	case "arm64":
    63  		return "arm64"
    64  	default:
    65  		return fmt.Sprintf("other:%s", runtime.GOARCH)
    66  	}
    67  }
    68  
    69  func getPlatformProperties() map[string]string {
    70  	return map[string]string{
    71  		"X-Stainless-Lang":            "go",
    72  		"X-Stainless-Package-Version": internal.PackageVersion,
    73  		"X-Stainless-OS":              getNormalizedOS(),
    74  		"X-Stainless-Arch":            getNormalizedArchitecture(),
    75  		"X-Stainless-Runtime":         "go",
    76  		"X-Stainless-Runtime-Version": runtime.Version(),
    77  	}
    78  }
    79  
    80  type RequestOption interface {
    81  	Apply(*RequestConfig) error
    82  }
    83  
    84  type RequestOptionFunc func(*RequestConfig) error
    85  type PreRequestOptionFunc func(*RequestConfig) error
    86  
    87  func (s RequestOptionFunc) Apply(r *RequestConfig) error    { return s(r) }
    88  func (s PreRequestOptionFunc) Apply(r *RequestConfig) error { return s(r) }
    89  
    90  func NewRequestConfig(ctx context.Context, method string, u string, body any, dst any, opts ...RequestOption) (*RequestConfig, error) {
    91  	var reader io.Reader
    92  
    93  	contentType := "application/json"
    94  	hasSerializationFunc := false
    95  
    96  	if body, ok := body.(json.Marshaler); ok {
    97  		content, err := body.MarshalJSON()
    98  		if err != nil {
    99  			return nil, err
   100  		}
   101  		reader = bytes.NewBuffer(content)
   102  		hasSerializationFunc = true
   103  	}
   104  	if body, ok := body.(apiform.Marshaler); ok {
   105  		var (
   106  			content []byte
   107  			err     error
   108  		)
   109  		content, contentType, err = body.MarshalMultipart()
   110  		if err != nil {
   111  			return nil, err
   112  		}
   113  		reader = bytes.NewBuffer(content)
   114  		hasSerializationFunc = true
   115  	}
   116  	if body, ok := body.(apiquery.Queryer); ok {
   117  		hasSerializationFunc = true
   118  		q, err := body.URLQuery()
   119  		if err != nil {
   120  			return nil, err
   121  		}
   122  		params := q.Encode()
   123  		if params != "" {
   124  			parsed, _ := url.Parse(u)
   125  			if parsed.RawQuery != "" {
   126  				parsed.RawQuery = parsed.RawQuery + "&" + params
   127  				u = parsed.String()
   128  			} else {
   129  				u = u + "?" + params
   130  			}
   131  		}
   132  	}
   133  	if body, ok := body.([]byte); ok {
   134  		reader = bytes.NewBuffer(body)
   135  		hasSerializationFunc = true
   136  	}
   137  	if body, ok := body.(io.Reader); ok {
   138  		reader = body
   139  		hasSerializationFunc = true
   140  	}
   141  
   142  	// Fallback to json serialization if none of the serialization functions that we expect
   143  	// to see is present.
   144  	if body != nil && !hasSerializationFunc {
   145  		buf := new(bytes.Buffer)
   146  		enc := json.NewEncoder(buf)
   147  		enc.SetEscapeHTML(true)
   148  		if err := enc.Encode(body); err != nil {
   149  			return nil, err
   150  		}
   151  		reader = buf
   152  	}
   153  
   154  	req, err := http.NewRequestWithContext(ctx, method, u, nil)
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  	if reader != nil {
   159  		req.Header.Set("Content-Type", contentType)
   160  	}
   161  
   162  	req.Header.Set("Accept", "application/json")
   163  	req.Header.Set("X-Stainless-Retry-Count", "0")
   164  	req.Header.Set("X-Stainless-Timeout", "0")
   165  	for k, v := range getDefaultHeaders() {
   166  		req.Header.Add(k, v)
   167  	}
   168  	req.Header.Set("anthropic-version", "2023-06-01")
   169  	for k, v := range getPlatformProperties() {
   170  		req.Header.Add(k, v)
   171  	}
   172  	cfg := RequestConfig{
   173  		MaxRetries: 2,
   174  		Context:    ctx,
   175  		Request:    req,
   176  		HTTPClient: http.DefaultClient,
   177  		Body:       reader,
   178  	}
   179  	cfg.ResponseBodyInto = dst
   180  	err = cfg.Apply(opts...)
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  
   185  	// This must run after `cfg.Apply(...)` above in case the request timeout gets modified. We also only
   186  	// apply our own logic for it if it's still "0" from above. If it's not, then it was deleted or modified
   187  	// by the user and we should respect that.
   188  	if req.Header.Get("X-Stainless-Timeout") == "0" {
   189  		if cfg.RequestTimeout == time.Duration(0) {
   190  			req.Header.Del("X-Stainless-Timeout")
   191  		} else {
   192  			req.Header.Set("X-Stainless-Timeout", strconv.Itoa(int(cfg.RequestTimeout.Seconds())))
   193  		}
   194  	}
   195  
   196  	return &cfg, nil
   197  }
   198  
   199  // This interface is primarily used to describe an [*http.Client], but also
   200  // supports custom HTTP implementations.
   201  type HTTPDoer interface {
   202  	Do(req *http.Request) (*http.Response, error)
   203  }
   204  
   205  // RequestConfig represents all the state related to one request.
   206  //
   207  // Editing the variables inside RequestConfig directly is unstable api. Prefer
   208  // composing the RequestOption instead if possible.
   209  type RequestConfig struct {
   210  	MaxRetries     int
   211  	RequestTimeout time.Duration
   212  	Context        context.Context
   213  	Request        *http.Request
   214  	BaseURL        *url.URL
   215  	// DefaultBaseURL will be used if BaseURL is not explicitly overridden using
   216  	// WithBaseURL.
   217  	DefaultBaseURL *url.URL
   218  	CustomHTTPDoer HTTPDoer
   219  	HTTPClient     *http.Client
   220  	Middlewares    []middleware
   221  	APIKey         string
   222  	AuthToken      string
   223  	WebhookKey     string
   224  	// If ResponseBodyInto not nil, then we will attempt to deserialize into
   225  	// ResponseBodyInto. If Destination is a []byte, then it will return the body as
   226  	// is.
   227  	ResponseBodyInto any
   228  	// ResponseInto copies the \*http.Response of the corresponding request into the
   229  	// given address
   230  	ResponseInto **http.Response
   231  	Body         io.Reader
   232  }
   233  
   234  // middleware is exactly the same type as the Middleware type found in the [option] package,
   235  // but it is redeclared here for circular dependency issues.
   236  type middleware = func(*http.Request, middlewareNext) (*http.Response, error)
   237  
   238  // middlewareNext is exactly the same type as the MiddlewareNext type found in the [option] package,
   239  // but it is redeclared here for circular dependency issues.
   240  type middlewareNext = func(*http.Request) (*http.Response, error)
   241  
   242  func applyMiddleware(middleware middleware, next middlewareNext) middlewareNext {
   243  	return func(req *http.Request) (res *http.Response, err error) {
   244  		return middleware(req, next)
   245  	}
   246  }
   247  
   248  func shouldRetry(req *http.Request, res *http.Response) bool {
   249  	// If there is no way to recover the Body, then we shouldn't retry.
   250  	if req.Body != nil && req.GetBody == nil {
   251  		return false
   252  	}
   253  
   254  	// If there is no response, that indicates that there is a connection error
   255  	// so we retry the request.
   256  	if res == nil {
   257  		return true
   258  	}
   259  
   260  	// If the header explicitly wants a retry behavior, respect that over the
   261  	// http status code.
   262  	if res.Header.Get("x-should-retry") == "true" {
   263  		return true
   264  	}
   265  	if res.Header.Get("x-should-retry") == "false" {
   266  		return false
   267  	}
   268  
   269  	return res.StatusCode == http.StatusRequestTimeout ||
   270  		res.StatusCode == http.StatusConflict ||
   271  		res.StatusCode == http.StatusTooManyRequests ||
   272  		res.StatusCode >= http.StatusInternalServerError
   273  }
   274  
   275  func parseRetryAfterHeader(resp *http.Response) (time.Duration, bool) {
   276  	if resp == nil {
   277  		return 0, false
   278  	}
   279  
   280  	type retryData struct {
   281  		header string
   282  		units  time.Duration
   283  
   284  		// custom is used when the regular algorithm failed and is optional.
   285  		// the returned duration is used verbatim (units is not applied).
   286  		custom func(string) (time.Duration, bool)
   287  	}
   288  
   289  	nop := func(string) (time.Duration, bool) { return 0, false }
   290  
   291  	// the headers are listed in order of preference
   292  	retries := []retryData{
   293  		{
   294  			header: "Retry-After-Ms",
   295  			units:  time.Millisecond,
   296  			custom: nop,
   297  		},
   298  		{
   299  			header: "Retry-After",
   300  			units:  time.Second,
   301  
   302  			// retry-after values are expressed in either number of
   303  			// seconds or an HTTP-date indicating when to try again
   304  			custom: func(ra string) (time.Duration, bool) {
   305  				t, err := time.Parse(time.RFC1123, ra)
   306  				if err != nil {
   307  					return 0, false
   308  				}
   309  				return time.Until(t), true
   310  			},
   311  		},
   312  	}
   313  
   314  	for _, retry := range retries {
   315  		v := resp.Header.Get(retry.header)
   316  		if v == "" {
   317  			continue
   318  		}
   319  		if retryAfter, err := strconv.ParseFloat(v, 64); err == nil {
   320  			return time.Duration(retryAfter * float64(retry.units)), true
   321  		}
   322  		if d, ok := retry.custom(v); ok {
   323  			return d, true
   324  		}
   325  	}
   326  
   327  	return 0, false
   328  }
   329  
   330  // isBeforeContextDeadline reports whether the non-zero Time t is
   331  // before ctx's deadline. If ctx does not have a deadline, it
   332  // always reports true (the deadline is considered infinite).
   333  func isBeforeContextDeadline(t time.Time, ctx context.Context) bool {
   334  	d, ok := ctx.Deadline()
   335  	if !ok {
   336  		return true
   337  	}
   338  	return t.Before(d)
   339  }
   340  
   341  // bodyWithTimeout is an io.ReadCloser which can observe a context's cancel func
   342  // to handle timeouts etc. It wraps an existing io.ReadCloser.
   343  type bodyWithTimeout struct {
   344  	stop func() // stops the time.Timer waiting to cancel the request
   345  	rc   io.ReadCloser
   346  }
   347  
   348  func (b *bodyWithTimeout) Read(p []byte) (n int, err error) {
   349  	n, err = b.rc.Read(p)
   350  	if err == nil {
   351  		return n, nil
   352  	}
   353  	if err == io.EOF {
   354  		return n, err
   355  	}
   356  	return n, err
   357  }
   358  
   359  func (b *bodyWithTimeout) Close() error {
   360  	err := b.rc.Close()
   361  	b.stop()
   362  	return err
   363  }
   364  
   365  func retryDelay(res *http.Response, retryCount int) time.Duration {
   366  	// If the backend tells us to wait a certain amount of time, use that value
   367  	if retryAfterDelay, ok := parseRetryAfterHeader(res); ok {
   368  		return max(0, retryAfterDelay)
   369  	}
   370  
   371  	maxDelay := 8 * time.Second
   372  	delay := time.Duration(0.5 * float64(time.Second) * math.Pow(2, float64(retryCount)))
   373  	if delay > maxDelay {
   374  		delay = maxDelay
   375  	}
   376  
   377  	jitter := rand.Int63n(int64(delay / 4))
   378  	delay -= time.Duration(jitter)
   379  	return delay
   380  }
   381  
   382  func (cfg *RequestConfig) Execute() (err error) {
   383  	if cfg.BaseURL == nil {
   384  		if cfg.DefaultBaseURL != nil {
   385  			cfg.BaseURL = cfg.DefaultBaseURL
   386  		} else {
   387  			return fmt.Errorf("requestconfig: base url is not set")
   388  		}
   389  	}
   390  
   391  	cfg.Request.URL, err = cfg.BaseURL.Parse(strings.TrimLeft(cfg.Request.URL.String(), "/"))
   392  	if err != nil {
   393  		return err
   394  	}
   395  
   396  	if cfg.Body != nil && cfg.Request.Body == nil {
   397  		switch body := cfg.Body.(type) {
   398  		case *bytes.Buffer:
   399  			b := body.Bytes()
   400  			cfg.Request.ContentLength = int64(body.Len())
   401  			cfg.Request.GetBody = func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(b)), nil }
   402  			cfg.Request.Body, _ = cfg.Request.GetBody()
   403  		case *bytes.Reader:
   404  			cfg.Request.ContentLength = int64(body.Len())
   405  			cfg.Request.GetBody = func() (io.ReadCloser, error) {
   406  				_, err := body.Seek(0, 0)
   407  				return io.NopCloser(body), err
   408  			}
   409  			cfg.Request.Body, _ = cfg.Request.GetBody()
   410  		default:
   411  			if rc, ok := body.(io.ReadCloser); ok {
   412  				cfg.Request.Body = rc
   413  			} else {
   414  				cfg.Request.Body = io.NopCloser(body)
   415  			}
   416  		}
   417  	}
   418  
   419  	handler := cfg.HTTPClient.Do
   420  	if cfg.CustomHTTPDoer != nil {
   421  		handler = cfg.CustomHTTPDoer.Do
   422  	}
   423  	for i := len(cfg.Middlewares) - 1; i >= 0; i -= 1 {
   424  		handler = applyMiddleware(cfg.Middlewares[i], handler)
   425  	}
   426  
   427  	// Don't send the current retry count in the headers if the caller modified the header defaults.
   428  	shouldSendRetryCount := cfg.Request.Header.Get("X-Stainless-Retry-Count") == "0"
   429  
   430  	var res *http.Response
   431  	var cancel context.CancelFunc
   432  	for retryCount := 0; retryCount <= cfg.MaxRetries; retryCount += 1 {
   433  		// callerCtx spans every attempt; ctx additionally carries this attempt's RequestTimeout, if any.
   434  		callerCtx := cfg.Request.Context()
   435  		ctx := callerCtx
   436  		// Release the previous attempt's timer before starting a new attempt.
   437  		if cancel != nil {
   438  			cancel()
   439  			cancel = nil
   440  		}
   441  		if cfg.RequestTimeout != time.Duration(0) && isBeforeContextDeadline(time.Now().Add(cfg.RequestTimeout), callerCtx) {
   442  			ctx, cancel = context.WithTimeout(callerCtx, cfg.RequestTimeout)
   443  			defer func() {
   444  				// The cancel function is nil if it was handed off to be handled in a different scope.
   445  				if cancel != nil {
   446  					cancel()
   447  				}
   448  			}()
   449  		}
   450  
   451  		req := cfg.Request.Clone(ctx)
   452  		if shouldSendRetryCount {
   453  			req.Header.Set("X-Stainless-Retry-Count", strconv.Itoa(retryCount))
   454  		}
   455  
   456  		res, err = handler(req)
   457  		// Once the caller's context is done there is nothing left to retry.
   458  		if callerErr := callerCtx.Err(); callerErr != nil {
   459  			return callerErr
   460  		}
   461  		// Only the per-attempt timeout expired: treat it like a connection error so it is retried,
   462  		// and surface the context error if this was the last attempt.
   463  		if ctxErr := ctx.Err(); ctxErr != nil {
   464  			if res != nil && res.Body != nil {
   465  				_ = res.Body.Close()
   466  			}
   467  			res = nil
   468  			err = ctxErr
   469  		}
   470  		if !shouldRetry(cfg.Request, res) || retryCount >= cfg.MaxRetries {
   471  			break
   472  		}
   473  
   474  		// Prepare next request and wait for the retry delay
   475  		if cfg.Request.GetBody != nil {
   476  			cfg.Request.Body, err = cfg.Request.GetBody()
   477  			if err != nil {
   478  				return err
   479  			}
   480  		}
   481  
   482  		// Can't actually refresh the body, so we don't attempt to retry here
   483  		if cfg.Request.GetBody == nil && cfg.Request.Body != nil {
   484  			break
   485  		}
   486  
   487  		// Close the response body before retrying to prevent connection leaks
   488  		if res != nil && res.Body != nil {
   489  			_ = res.Body.Close()
   490  		}
   491  
   492  		select {
   493  		case <-callerCtx.Done():
   494  			return callerCtx.Err()
   495  		case <-time.After(retryDelay(res, retryCount)):
   496  		}
   497  	}
   498  
   499  	// Save *http.Response if it is requested to, even if there was an error making the request. This is
   500  	// useful in cases where you might want to debug by inspecting the response. Note that if err != nil,
   501  	// the response should be generally be empty, but there are edge cases.
   502  	if cfg.ResponseInto != nil {
   503  		*cfg.ResponseInto = res
   504  	}
   505  	if responseBodyInto, ok := cfg.ResponseBodyInto.(**http.Response); ok {
   506  		*responseBodyInto = res
   507  	}
   508  
   509  	// If there was a connection error in the final request or any other transport error,
   510  	// return that early without trying to coerce into an APIError.
   511  	if err != nil {
   512  		return err
   513  	}
   514  
   515  	if res.StatusCode >= 400 {
   516  		contents, err := io.ReadAll(res.Body)
   517  		_ = res.Body.Close()
   518  		if err != nil {
   519  			return err
   520  		}
   521  
   522  		// If there is an APIError, re-populate the response body so that debugging
   523  		// utilities can conveniently dump the response without issue.
   524  		res.Body = io.NopCloser(bytes.NewBuffer(contents))
   525  
   526  		// Load the contents into the error format if it is provided.
   527  		aerr := apierror.Error{Request: cfg.Request, Response: res, StatusCode: res.StatusCode, RequestID: res.Header.Get("request-id"), WorkspaceID: res.Header.Get("anthropic-workspace-id")}
   528  		err = aerr.UnmarshalJSON(contents)
   529  		if err != nil {
   530  			return err
   531  		}
   532  		return &aerr
   533  	}
   534  
   535  	_, intoCustomResponseBody := cfg.ResponseBodyInto.(**http.Response)
   536  	if cfg.ResponseBodyInto == nil || intoCustomResponseBody {
   537  		// We aren't reading the response body in this scope, but whoever is will need the
   538  		// cancel func from the context to observe request timeouts.
   539  		// Put the cancel function in the response body so it can be handled elsewhere.
   540  		if cancel != nil {
   541  			res.Body = &bodyWithTimeout{rc: res.Body, stop: cancel}
   542  			cancel = nil
   543  		}
   544  		return nil
   545  	}
   546  
   547  	contents, err := io.ReadAll(res.Body)
   548  	_ = res.Body.Close()
   549  	if err != nil {
   550  		return fmt.Errorf("error reading response body: %w", err)
   551  	}
   552  
   553  	// If we are not json, return plaintext
   554  	contentType := res.Header.Get("content-type")
   555  	mediaType, _, _ := mime.ParseMediaType(contentType)
   556  	isJSON := strings.Contains(mediaType, "application/json") || strings.HasSuffix(mediaType, "+json")
   557  	if !isJSON {
   558  		switch dst := cfg.ResponseBodyInto.(type) {
   559  		case *string:
   560  			*dst = string(contents)
   561  		case **string:
   562  			tmp := string(contents)
   563  			*dst = &tmp
   564  		case *[]byte:
   565  			*dst = contents
   566  		default:
   567  			return fmt.Errorf("expected destination type of 'string' or '[]byte' for responses with content-type '%s' that is not 'application/json'", contentType)
   568  		}
   569  		return nil
   570  	}
   571  
   572  	switch dst := cfg.ResponseBodyInto.(type) {
   573  	// If the response happens to be a byte array, deserialize the body as-is.
   574  	case *[]byte:
   575  		*dst = contents
   576  	default:
   577  		err = json.NewDecoder(bytes.NewReader(contents)).Decode(cfg.ResponseBodyInto)
   578  		if err != nil {
   579  			return fmt.Errorf("error parsing response json: %w", err)
   580  		}
   581  	}
   582  
   583  	return nil
   584  }
   585  
   586  func ExecuteNewRequest(ctx context.Context, method string, u string, body any, dst any, opts ...RequestOption) error {
   587  	cfg, err := NewRequestConfig(ctx, method, u, body, dst, opts...)
   588  	if err != nil {
   589  		return err
   590  	}
   591  	return cfg.Execute()
   592  }
   593  
   594  func (cfg *RequestConfig) Clone(ctx context.Context) *RequestConfig {
   595  	if cfg == nil {
   596  		return nil
   597  	}
   598  	req := cfg.Request.Clone(ctx)
   599  	var err error
   600  	if req.Body != nil {
   601  		req.Body, err = req.GetBody()
   602  	}
   603  	if err != nil {
   604  		return nil
   605  	}
   606  	new := &RequestConfig{
   607  		MaxRetries:     cfg.MaxRetries,
   608  		RequestTimeout: cfg.RequestTimeout,
   609  		Context:        ctx,
   610  		Request:        req,
   611  		BaseURL:        cfg.BaseURL,
   612  		HTTPClient:     cfg.HTTPClient,
   613  		Middlewares:    cfg.Middlewares,
   614  		APIKey:         cfg.APIKey,
   615  		AuthToken:      cfg.AuthToken,
   616  		WebhookKey:     cfg.WebhookKey,
   617  	}
   618  
   619  	return new
   620  }
   621  
   622  func (cfg *RequestConfig) Apply(opts ...RequestOption) error {
   623  	for _, opt := range opts {
   624  		err := opt.Apply(cfg)
   625  		if err != nil {
   626  			return err
   627  		}
   628  	}
   629  	return nil
   630  }
   631  
   632  // PreRequestOptions is used to collect all the options which need to be known before
   633  // a call to [RequestConfig.ExecuteNewRequest], such as path parameters
   634  // or global defaults.
   635  // PreRequestOptions will return a [RequestConfig] with the options applied.
   636  //
   637  // Only request option functions of type [PreRequestOptionFunc] are applied.
   638  func PreRequestOptions(opts ...RequestOption) (RequestConfig, error) {
   639  	cfg := RequestConfig{}
   640  	for _, opt := range opts {
   641  		if opt, ok := opt.(PreRequestOptionFunc); ok {
   642  			err := opt.Apply(&cfg)
   643  			if err != nil {
   644  				return cfg, err
   645  			}
   646  		}
   647  	}
   648  	return cfg, nil
   649  }
   650  
   651  // WithDefaultBaseURL returns a RequestOption that sets the client's default Base URL.
   652  // This is always overridden by setting a base URL with WithBaseURL.
   653  // WithBaseURL should be used instead of WithDefaultBaseURL except in internal code.
   654  func WithDefaultBaseURL(baseURL string) RequestOption {
   655  	u, err := url.Parse(baseURL)
   656  	return RequestOptionFunc(func(r *RequestConfig) error {
   657  		if err != nil {
   658  			return err
   659  		}
   660  		r.DefaultBaseURL = u
   661  		return nil
   662  	})
   663  }