requestoption.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
  2. package option
  3. import (
  4. "bytes"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "net/url"
  9. "strings"
  10. "time"
  11. "github.com/sst/opencode-sdk-go/internal/requestconfig"
  12. "github.com/tidwall/sjson"
  13. )
  14. // RequestOption is an option for the requests made by the opencode API Client
  15. // which can be supplied to clients, services, and methods. You can read more about this functional
  16. // options pattern in our [README].
  17. //
  18. // [README]: https://pkg.go.dev/github.com/sst/opencode-sdk-go#readme-requestoptions
  19. type RequestOption = requestconfig.RequestOption
  20. // WithBaseURL returns a RequestOption that sets the BaseURL for the client.
  21. //
  22. // For security reasons, ensure that the base URL is trusted.
  23. func WithBaseURL(base string) RequestOption {
  24. u, err := url.Parse(base)
  25. if err == nil && u.Path != "" && !strings.HasSuffix(u.Path, "/") {
  26. u.Path += "/"
  27. }
  28. return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
  29. if err != nil {
  30. return fmt.Errorf("requestoption: WithBaseURL failed to parse url %s", err)
  31. }
  32. r.BaseURL = u
  33. return nil
  34. })
  35. }
  36. // HTTPClient is primarily used to describe an [*http.Client], but also
  37. // supports custom implementations.
  38. //
  39. // For bespoke implementations, prefer using an [*http.Client] with a
  40. // custom transport. See [http.RoundTripper] for further information.
  41. type HTTPClient interface {
  42. Do(*http.Request) (*http.Response, error)
  43. }
  44. // WithHTTPClient returns a RequestOption that changes the underlying http client used to make this
  45. // request, which by default is [http.DefaultClient].
  46. //
  47. // For custom uses cases, it is recommended to provide an [*http.Client] with a custom
  48. // [http.RoundTripper] as its transport, rather than directly implementing [HTTPClient].
  49. func WithHTTPClient(client HTTPClient) RequestOption {
  50. return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
  51. if client == nil {
  52. return fmt.Errorf("requestoption: custom http client cannot be nil")
  53. }
  54. if c, ok := client.(*http.Client); ok {
  55. // Prefer the native client if possible.
  56. r.HTTPClient = c
  57. r.CustomHTTPDoer = nil
  58. } else {
  59. r.CustomHTTPDoer = client
  60. }
  61. return nil
  62. })
  63. }
  64. // MiddlewareNext is a function which is called by a middleware to pass an HTTP request
  65. // to the next stage in the middleware chain.
  66. type MiddlewareNext = func(*http.Request) (*http.Response, error)
  67. // Middleware is a function which intercepts HTTP requests, processing or modifying
  68. // them, and then passing the request to the next middleware or handler
  69. // in the chain by calling the provided MiddlewareNext function.
  70. type Middleware = func(*http.Request, MiddlewareNext) (*http.Response, error)
  71. // WithMiddleware returns a RequestOption that applies the given middleware
  72. // to the requests made. Each middleware will execute in the order they were given.
  73. func WithMiddleware(middlewares ...Middleware) RequestOption {
  74. return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
  75. r.Middlewares = append(r.Middlewares, middlewares...)
  76. return nil
  77. })
  78. }
  79. // WithMaxRetries returns a RequestOption that sets the maximum number of retries that the client
  80. // attempts to make. When given 0, the client only makes one request. By
  81. // default, the client retries two times.
  82. //
  83. // WithMaxRetries panics when retries is negative.
  84. func WithMaxRetries(retries int) RequestOption {
  85. if retries < 0 {
  86. panic("option: cannot have fewer than 0 retries")
  87. }
  88. return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
  89. r.MaxRetries = retries
  90. return nil
  91. })
  92. }
  93. // WithHeader returns a RequestOption that sets the header value to the associated key. It overwrites
  94. // any value if there was one already present.
  95. func WithHeader(key, value string) RequestOption {
  96. return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
  97. r.Request.Header.Set(key, value)
  98. return nil
  99. })
  100. }
  101. // WithHeaderAdd returns a RequestOption that adds the header value to the associated key. It appends
  102. // onto any existing values.
  103. func WithHeaderAdd(key, value string) RequestOption {
  104. return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
  105. r.Request.Header.Add(key, value)
  106. return nil
  107. })
  108. }
  109. // WithHeaderDel returns a RequestOption that deletes the header value(s) associated with the given key.
  110. func WithHeaderDel(key string) RequestOption {
  111. return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
  112. r.Request.Header.Del(key)
  113. return nil
  114. })
  115. }
  116. // WithQuery returns a RequestOption that sets the query value to the associated key. It overwrites
  117. // any value if there was one already present.
  118. func WithQuery(key, value string) RequestOption {
  119. return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
  120. query := r.Request.URL.Query()
  121. query.Set(key, value)
  122. r.Request.URL.RawQuery = query.Encode()
  123. return nil
  124. })
  125. }
  126. // WithQueryAdd returns a RequestOption that adds the query value to the associated key. It appends
  127. // onto any existing values.
  128. func WithQueryAdd(key, value string) RequestOption {
  129. return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
  130. query := r.Request.URL.Query()
  131. query.Add(key, value)
  132. r.Request.URL.RawQuery = query.Encode()
  133. return nil
  134. })
  135. }
  136. // WithQueryDel returns a RequestOption that deletes the query value(s) associated with the key.
  137. func WithQueryDel(key string) RequestOption {
  138. return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
  139. query := r.Request.URL.Query()
  140. query.Del(key)
  141. r.Request.URL.RawQuery = query.Encode()
  142. return nil
  143. })
  144. }
  145. // WithJSONSet returns a RequestOption that sets the body's JSON value associated with the key.
  146. // The key accepts a string as defined by the [sjson format].
  147. //
  148. // [sjson format]: https://github.com/tidwall/sjson
  149. func WithJSONSet(key string, value interface{}) RequestOption {
  150. return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) (err error) {
  151. var b []byte
  152. if r.Body == nil {
  153. b, err = sjson.SetBytes(nil, key, value)
  154. if err != nil {
  155. return err
  156. }
  157. } else if buffer, ok := r.Body.(*bytes.Buffer); ok {
  158. b = buffer.Bytes()
  159. b, err = sjson.SetBytes(b, key, value)
  160. if err != nil {
  161. return err
  162. }
  163. } else {
  164. return fmt.Errorf("cannot use WithJSONSet on a body that is not serialized as *bytes.Buffer")
  165. }
  166. r.Body = bytes.NewBuffer(b)
  167. return nil
  168. })
  169. }
  170. // WithJSONDel returns a RequestOption that deletes the body's JSON value associated with the key.
  171. // The key accepts a string as defined by the [sjson format].
  172. //
  173. // [sjson format]: https://github.com/tidwall/sjson
  174. func WithJSONDel(key string) RequestOption {
  175. return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) (err error) {
  176. if buffer, ok := r.Body.(*bytes.Buffer); ok {
  177. b := buffer.Bytes()
  178. b, err = sjson.DeleteBytes(b, key)
  179. if err != nil {
  180. return err
  181. }
  182. r.Body = bytes.NewBuffer(b)
  183. return nil
  184. }
  185. return fmt.Errorf("cannot use WithJSONDel on a body that is not serialized as *bytes.Buffer")
  186. })
  187. }
  188. // WithResponseBodyInto returns a RequestOption that overwrites the deserialization target with
  189. // the given destination. If provided, we don't deserialize into the default struct.
  190. func WithResponseBodyInto(dst any) RequestOption {
  191. return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
  192. r.ResponseBodyInto = dst
  193. return nil
  194. })
  195. }
  196. // WithResponseInto returns a RequestOption that copies the [*http.Response] into the given address.
  197. func WithResponseInto(dst **http.Response) RequestOption {
  198. return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
  199. r.ResponseInto = dst
  200. return nil
  201. })
  202. }
  203. // WithRequestBody returns a RequestOption that provides a custom serialized body with the given
  204. // content type.
  205. //
  206. // body accepts an io.Reader or raw []bytes.
  207. func WithRequestBody(contentType string, body any) RequestOption {
  208. return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
  209. if reader, ok := body.(io.Reader); ok {
  210. r.Body = reader
  211. return r.Apply(WithHeader("Content-Type", contentType))
  212. }
  213. if b, ok := body.([]byte); ok {
  214. r.Body = bytes.NewBuffer(b)
  215. return r.Apply(WithHeader("Content-Type", contentType))
  216. }
  217. return fmt.Errorf("body must be a byte slice or implement io.Reader")
  218. })
  219. }
  220. // WithRequestTimeout returns a RequestOption that sets the timeout for
  221. // each request attempt. This should be smaller than the timeout defined in
  222. // the context, which spans all retries.
  223. func WithRequestTimeout(dur time.Duration) RequestOption {
  224. return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
  225. r.RequestTimeout = dur
  226. return nil
  227. })
  228. }
  229. // WithEnvironmentProduction returns a RequestOption that sets the current
  230. // environment to be the "production" environment. An environment specifies which base URL
  231. // to use by default.
  232. func WithEnvironmentProduction() RequestOption {
  233. return requestconfig.WithDefaultBaseURL("http://localhost:54321/")
  234. }