Add stub for doll-master and doll services, add required vendored libs
This commit is contained in:
1
vendor/github.com/carlmjohnson/requests/.gitignore
generated
vendored
Normal file
1
vendor/github.com/carlmjohnson/requests/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
img/
|
||||
21
vendor/github.com/carlmjohnson/requests/LICENSE
generated
vendored
Normal file
21
vendor/github.com/carlmjohnson/requests/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Carl Johnson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
314
vendor/github.com/carlmjohnson/requests/README.md
generated
vendored
Normal file
314
vendor/github.com/carlmjohnson/requests/README.md
generated
vendored
Normal file
@@ -0,0 +1,314 @@
|
||||
# Requests [](https://godoc.org/github.com/carlmjohnson/requests) [](https://goreportcard.com/report/github.com/carlmjohnson/requests) [](https://coveralls.io/github/earthboundkid/requests) [](https://github.com/avelino/awesome-go)
|
||||
|
||||

|
||||
|
||||
## _HTTP requests for Gophers._
|
||||
|
||||
**The problem**: Go's net/http is powerful and versatile, but using it correctly for client requests can be extremely verbose.
|
||||
|
||||
**The solution**: The requests.Builder type is a convenient way to build, send, and handle HTTP requests. Builder has a fluent API with methods returning a pointer to the same struct, which allows for declaratively describing a request by method chaining.
|
||||
|
||||
Requests also comes with tools for building custom http transports, include a request recorder and replayer for testing.
|
||||
|
||||
*[See this note on the canonical project URL.](https://gist.github.com/earthboundkid/8915002ae0e531cecdfc58bc6453ac80)*
|
||||
|
||||
## Features
|
||||
|
||||
- Simplifies HTTP client usage compared to net/http
|
||||
- Can't forget to close response body
|
||||
- Checks status codes by default
|
||||
- Supports context.Context
|
||||
- JSON serialization and deserialization helpers
|
||||
- Easily manipulate URLs and query parameters
|
||||
- Request recording and replaying for tests
|
||||
- Customizable transports and validators that are compatible with the standard library and third party libraries
|
||||
- No third party dependencies
|
||||
- Good test coverage
|
||||
|
||||
## Examples
|
||||
### Simple GET into a string
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th><strong>code with net/http</strong></th>
|
||||
<th><strong>code with requests</strong></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
```go
|
||||
req, err := http.NewRequestWithContext(ctx,
|
||||
http.MethodGet, "http://example.com", nil)
|
||||
if err != nil {
|
||||
// ...
|
||||
}
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
// ...
|
||||
}
|
||||
defer res.Body.Close()
|
||||
b, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
// ...
|
||||
}
|
||||
s := string(b)
|
||||
```
|
||||
</td>
|
||||
<td>
|
||||
|
||||
```go
|
||||
var s string
|
||||
err := requests.
|
||||
URL("http://example.com").
|
||||
ToString(&s).
|
||||
Fetch(ctx)
|
||||
```
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td>11+ lines</td><td>5 lines</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
### POST a raw body
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th><strong>code with net/http</strong></th>
|
||||
<th><strong>code with requests</strong></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
```go
|
||||
body := bytes.NewReader(([]byte(`hello, world`))
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
|
||||
"https://postman-echo.com/post", body)
|
||||
if err != nil {
|
||||
// ...
|
||||
}
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
// ...
|
||||
}
|
||||
defer res.Body.Close()
|
||||
_, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
```go
|
||||
err := requests.
|
||||
URL("https://postman-echo.com/post").
|
||||
BodyBytes([]byte(`hello, world`)).
|
||||
ContentType("text/plain").
|
||||
Fetch(ctx)
|
||||
```
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td>12+ lines</td><td>5 lines</td></tr></tbody></table>
|
||||
|
||||
### GET a JSON object
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th><strong>code with net/http</strong></th>
|
||||
<th><strong>code with requests</strong></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
```go
|
||||
var post placeholder
|
||||
u, err := url.Parse("https://jsonplaceholder.typicode.com")
|
||||
if err != nil {
|
||||
// ...
|
||||
}
|
||||
u.Path = fmt.Sprintf("/posts/%d", 1)
|
||||
req, err := http.NewRequestWithContext(ctx,
|
||||
http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
// ...
|
||||
}
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
// ...
|
||||
}
|
||||
defer res.Body.Close()
|
||||
b, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
// ...
|
||||
}
|
||||
err := json.Unmarshal(b, &post)
|
||||
if err != nil {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
</td><td>
|
||||
|
||||
```go
|
||||
var post placeholder
|
||||
err := requests.
|
||||
URL("https://jsonplaceholder.typicode.com").
|
||||
Pathf("/posts/%d", 1).
|
||||
ToJSON(&post).
|
||||
Fetch(ctx)
|
||||
```
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td>18+ lines</td><td>7 lines</td></tr></tbody></table>
|
||||
|
||||
### POST a JSON object and parse the response
|
||||
|
||||
```go
|
||||
var res placeholder
|
||||
req := placeholder{
|
||||
Title: "foo",
|
||||
Body: "baz",
|
||||
UserID: 1,
|
||||
}
|
||||
err := requests.
|
||||
URL("/posts").
|
||||
Host("jsonplaceholder.typicode.com").
|
||||
BodyJSON(&req).
|
||||
ToJSON(&res).
|
||||
Fetch(ctx)
|
||||
// net/http equivalent left as an exercise for the reader
|
||||
```
|
||||
|
||||
### Set custom headers for a request
|
||||
|
||||
```go
|
||||
// Set headers
|
||||
var headers postman
|
||||
err := requests.
|
||||
URL("https://postman-echo.com/get").
|
||||
UserAgent("bond/james-bond").
|
||||
ContentType("secret").
|
||||
Header("martini", "shaken").
|
||||
Fetch(ctx)
|
||||
```
|
||||
|
||||
### Easily manipulate URLs and query parameters
|
||||
|
||||
```go
|
||||
u, err := requests.
|
||||
URL("https://prod.example.com/get?a=1&b=2").
|
||||
Hostf("%s.example.com", "dev1").
|
||||
Param("b", "3").
|
||||
ParamInt("c", 4).
|
||||
URL()
|
||||
if err != nil { /* ... */ }
|
||||
fmt.Println(u.String()) // https://dev1.example.com/get?a=1&b=3&c=4
|
||||
```
|
||||
|
||||
### Record and replay responses
|
||||
|
||||
```go
|
||||
// record a request to the file system
|
||||
var s1, s2 string
|
||||
err := requests.URL("http://example.com").
|
||||
Transport(reqtest.Record(nil, "somedir")).
|
||||
ToString(&s1).
|
||||
Fetch(ctx)
|
||||
check(err)
|
||||
|
||||
// now replay the request in tests
|
||||
err = requests.URL("http://example.com").
|
||||
Transport(reqtest.Replay("somedir")).
|
||||
ToString(&s2).
|
||||
Fetch(ctx)
|
||||
check(err)
|
||||
assert(s1 == s2) // true
|
||||
```
|
||||
|
||||
## FAQs
|
||||
|
||||
[See wiki](https://github.com/earthboundkid/requests/wiki) for more details.
|
||||
|
||||
### Why not just use the standard library HTTP client?
|
||||
|
||||
Brad Fitzpatrick, long time maintainer of the net/http package, [wrote an extensive list of problems with the standard library HTTP client](https://github.com/bradfitz/exp-httpclient/blob/master/problems.md). His four main points (ignoring issues that can't be resolved by a wrapper around the standard library) are:
|
||||
|
||||
> - Too easy to not call Response.Body.Close.
|
||||
> - Too easy to not check return status codes
|
||||
> - Context support is oddly bolted on
|
||||
> - Proper usage is too many lines of boilerplate
|
||||
|
||||
Requests solves these issues by always closing the response body, checking status codes by default, always requiring a `context.Context`, and simplifying the boilerplate with a descriptive UI based on fluent method chaining.
|
||||
|
||||
### Why requests and not some other helper library?
|
||||
|
||||
There are two major flaws in other libraries as I see it. One is that in other libraries support for `context.Context` tends to be bolted on if it exists at all. Two, many hide the underlying `http.Client` in such a way that it is difficult or impossible to replace or mock out. Beyond that, I believe that none have achieved the same core simplicity that the requests library has.
|
||||
|
||||
### How do I just get some JSON?
|
||||
|
||||
```go
|
||||
var data SomeDataType
|
||||
err := requests.
|
||||
URL("https://example.com/my-json").
|
||||
ToJSON(&data).
|
||||
Fetch(ctx)
|
||||
```
|
||||
|
||||
### How do I post JSON and read the response JSON?
|
||||
|
||||
```go
|
||||
body := MyRequestType{}
|
||||
var resp MyResponseType
|
||||
err := requests.
|
||||
URL("https://example.com/my-json").
|
||||
BodyJSON(&body).
|
||||
ToJSON(&resp).
|
||||
Fetch(ctx)
|
||||
```
|
||||
|
||||
### How do I just save a file to disk?
|
||||
|
||||
It depends on exactly what you need in terms of file atomicity and buffering, but this will work for most cases:
|
||||
|
||||
```go
|
||||
err := requests.
|
||||
URL("http://example.com").
|
||||
ToFile("myfile.txt").
|
||||
Fetch(ctx)
|
||||
```
|
||||
|
||||
For more advanced use case, use `ToWriter`.
|
||||
|
||||
### How do I save a response to a string?
|
||||
|
||||
```go
|
||||
var s string
|
||||
err := requests.
|
||||
URL("http://example.com").
|
||||
ToString(&s).
|
||||
Fetch(ctx)
|
||||
```
|
||||
|
||||
### How do I validate the response status?
|
||||
|
||||
By default, if no other validators are added to a builder, requests will check that the response is in the 2XX range. If you add another validator, you can add `builder.CheckStatus(200)` or `builder.AddValidator(requests.DefaultValidator)` to the validation stack.
|
||||
|
||||
To disable all response validation, run `builder.AddValidator(nil)`.
|
||||
|
||||
## Contributing
|
||||
|
||||
Please [create a discussion](https://github.com/earthboundkid/requests/discussions) before submitting a pull request for a new feature.
|
||||
9
vendor/github.com/carlmjohnson/requests/SECURITY.md
generated
vendored
Normal file
9
vendor/github.com/carlmjohnson/requests/SECURITY.md
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Only the most recently tagged version of this repository is supported.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Email me@carlmjohnson.net with any security concerns or vulnerabilities.
|
||||
77
vendor/github.com/carlmjohnson/requests/body.go
generated
vendored
Normal file
77
vendor/github.com/carlmjohnson/requests/body.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BodyGetter provides a Builder with a source for a request body.
|
||||
type BodyGetter = func() (io.ReadCloser, error)
|
||||
|
||||
// BodyReader is a BodyGetter that returns an io.Reader.
|
||||
func BodyReader(r io.Reader) BodyGetter {
|
||||
return func() (io.ReadCloser, error) {
|
||||
if rc, ok := r.(io.ReadCloser); ok {
|
||||
return rc, nil
|
||||
}
|
||||
return rc(r), nil
|
||||
}
|
||||
}
|
||||
|
||||
// BodyWriter is a BodyGetter that pipes writes into a request body.
|
||||
func BodyWriter(f func(w io.Writer) error) BodyGetter {
|
||||
return func() (io.ReadCloser, error) {
|
||||
r, w := io.Pipe()
|
||||
go func() {
|
||||
var err error
|
||||
defer func() {
|
||||
w.CloseWithError(err)
|
||||
}()
|
||||
err = f(w)
|
||||
}()
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
|
||||
// BodyBytes is a BodyGetter that returns the provided raw bytes.
|
||||
func BodyBytes(b []byte) BodyGetter {
|
||||
return func() (io.ReadCloser, error) {
|
||||
return rc(bytes.NewReader(b)), nil
|
||||
}
|
||||
}
|
||||
|
||||
// BodySerializer is a BodyGetter
|
||||
// that uses the provided [Serializer]
|
||||
// to build the body of a request from v.
|
||||
func BodySerializer(s Serializer, v any) BodyGetter {
|
||||
return func() (io.ReadCloser, error) {
|
||||
b, err := s(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rc(bytes.NewReader(b)), nil
|
||||
}
|
||||
}
|
||||
|
||||
// BodyJSON is a [BodySerializer]
|
||||
// that uses [JSONSerializer] to marshal the object.
|
||||
func BodyJSON(v any) BodyGetter {
|
||||
return BodySerializer(JSONSerializer, v)
|
||||
}
|
||||
|
||||
// BodyForm is a BodyGetter that builds an encoded form body.
|
||||
func BodyForm(data url.Values) BodyGetter {
|
||||
return func() (r io.ReadCloser, err error) {
|
||||
return rc(strings.NewReader(data.Encode())), nil
|
||||
}
|
||||
}
|
||||
|
||||
// BodyFile is a BodyGetter that reads the provided file path.
|
||||
func BodyFile(name string) BodyGetter {
|
||||
return func() (r io.ReadCloser, err error) {
|
||||
return os.Open(name)
|
||||
}
|
||||
}
|
||||
281
vendor/github.com/carlmjohnson/requests/builder_core.go
generated
vendored
Normal file
281
vendor/github.com/carlmjohnson/requests/builder_core.go
generated
vendored
Normal file
@@ -0,0 +1,281 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
|
||||
"github.com/carlmjohnson/requests/internal/minitrue"
|
||||
)
|
||||
|
||||
// Builder is a convenient way to build, send, and handle HTTP requests.
|
||||
// Builder has a fluent API with methods returning a pointer to the same
|
||||
// struct, which allows for declaratively describing a request by method chaining.
|
||||
//
|
||||
// Builder can build a url.URL,
|
||||
// build an http.Request,
|
||||
// or handle a full http.Client request and response with validation.
|
||||
//
|
||||
// # Build a url.URL with Builder.URL
|
||||
//
|
||||
// Set the base URL by creating a new Builder with [requests.URL]
|
||||
// or by calling [Builder.BaseURL]
|
||||
// then customize it with
|
||||
// [Builder.Scheme], [Builder.Host], [Builder.Hostf], [Builder.Path],
|
||||
// [Builder.Pathf], [Builder.Param], and [Builder.ParamInt].
|
||||
// [Builder.ParamOptional] can be used to add a query parameter
|
||||
// only if it has not been otherwise set.
|
||||
//
|
||||
// # Build an http.Request with Builder.Request
|
||||
//
|
||||
// Set the method for a request with [Builder.Method]
|
||||
// or use the [Builder.Delete], [Builder.Head], [Builder.Patch], [Builder.Post], and [Builder.Put] methods.
|
||||
// By default, requests without a body are GET,
|
||||
// and those with a body are POST.
|
||||
//
|
||||
// Set headers with [Builder.Header]
|
||||
// or set conventional header keys with
|
||||
// [Builder.Accept], [Builder.BasicAuth], [Builder.Bearer], [Builder.CacheControl],
|
||||
// [Builder.ContentType], [Builder.Cookie], and [Builder.UserAgent].
|
||||
// [Builder.HeaderOptional] can be used to add a header
|
||||
// only if it has not been otherwise set.
|
||||
//
|
||||
// Set the body of the request, if any, with [Builder.Body]
|
||||
// or use built in [Builder.BodyBytes], [Builder.BodyFile], [Builder.BodyForm],
|
||||
// [Builder.BodyJSON], [Builder.BodyReader], or [Builder.BodyWriter].
|
||||
//
|
||||
// # Handle a request and response with Builder.Do or Builder.Fetch
|
||||
//
|
||||
// Set the http.Client to use for a request with [Builder.Client]
|
||||
// and/or set an http.RoundTripper with [Builder.Transport].
|
||||
//
|
||||
// Add a response validator to the Builder with [Builder.AddValidator]
|
||||
// or use the built in [Builder.CheckStatus], [Builder.CheckContentType],
|
||||
// [Builder.CheckPeek], [Builder.CopyHeaders], and [Builder.ErrorJSON].
|
||||
// If no validator has been added, Builder will use [DefaultValidator].
|
||||
//
|
||||
// Set a handler for a response with [Builder.Handle]
|
||||
// or use the built in [Builder.ToHeaders], [Builder.ToJSON], [Builder.ToString],
|
||||
// [Builder.ToBytesBuffer], or [Builder.ToWriter].
|
||||
//
|
||||
// [Builder.Fetch] creates an http.Request with [Builder.Request]
|
||||
// and validates and handles it with [Builder.Do].
|
||||
//
|
||||
// # Other methods
|
||||
//
|
||||
// [Builder.Config] can be used to set several options on a Builder at once.
|
||||
// [New] creates a new Builder and applies [Config] options to it.
|
||||
//
|
||||
// In many cases, it will be possible to set most options for an API endpoint
|
||||
// in a Builder at the package or struct level
|
||||
// and then call [Builder.Clone] in a function
|
||||
// to add request specific details for the URL, parameters, headers, body, or handler.
|
||||
//
|
||||
// Errors returned by Builder methods will have an [ErrorKind] indicating their origin.
|
||||
//
|
||||
// The zero value of Builder is usable.
|
||||
type Builder struct {
|
||||
ub urlBuilder
|
||||
rb requestBuilder
|
||||
cl *http.Client
|
||||
rt http.RoundTripper
|
||||
validators []ResponseHandler
|
||||
handler ResponseHandler
|
||||
}
|
||||
|
||||
// BaseURL sets the base URL that other URL methods modify.
|
||||
// It is usually more convenient to use [URL] instead.
|
||||
func (rb *Builder) BaseURL(baseurl string) *Builder {
|
||||
rb.ub.BaseURL(baseurl)
|
||||
return rb
|
||||
}
|
||||
|
||||
// Scheme sets the scheme for a Builder's URL.
|
||||
// It overrides the scheme set by BaseURL.
|
||||
func (rb *Builder) Scheme(scheme string) *Builder {
|
||||
rb.ub.Scheme(scheme)
|
||||
return rb
|
||||
}
|
||||
|
||||
// Host sets the host for a Builder's URL.
|
||||
// It overrides the host set by BaseURL.
|
||||
func (rb *Builder) Host(host string) *Builder {
|
||||
rb.ub.Host(host)
|
||||
return rb
|
||||
}
|
||||
|
||||
// Path joins a path to a Builder's URL per the path joining rules of RFC 3986.
|
||||
// If the path begins with /, it overrides any existing path.
|
||||
// If the path begins with ./ or ../, the final path will be rewritten in its absolute form when creating a request.
|
||||
func (rb *Builder) Path(path string) *Builder {
|
||||
rb.ub.Path(path)
|
||||
return rb
|
||||
}
|
||||
|
||||
// Param sets a query parameter on a Builder's URL.
|
||||
// It overwrites the existing values of a key.
|
||||
func (rb *Builder) Param(key string, values ...string) *Builder {
|
||||
rb.ub.Param(key, values...)
|
||||
return rb
|
||||
}
|
||||
|
||||
// ParamOptional sets a query parameter on a Builder's URL
|
||||
// only if it is not set by some other call to Param or ParamOptional
|
||||
// and one of the values is a non-blank string.
|
||||
func (rb *Builder) ParamOptional(key string, values ...string) *Builder {
|
||||
rb.ub.ParamOptional(key, values...)
|
||||
return rb
|
||||
}
|
||||
|
||||
// Header sets a header on a request. It overwrites the existing values of a key.
|
||||
func (rb *Builder) Header(key string, values ...string) *Builder {
|
||||
rb.rb.Header(key, values...)
|
||||
return rb
|
||||
}
|
||||
|
||||
// HeaderOptional sets a header on a request
|
||||
// only if it has not already been set by another call to Header or HeaderOptional
|
||||
// and one of the values is a non-blank string.
|
||||
func (rb *Builder) HeaderOptional(key string, values ...string) *Builder {
|
||||
rb.rb.HeaderOptional(key, values...)
|
||||
return rb
|
||||
}
|
||||
|
||||
// Cookie adds a cookie to a request.
|
||||
// Unlike other headers, adding a cookie does not overwrite existing values.
|
||||
func (rb *Builder) Cookie(name, value string) *Builder {
|
||||
rb.rb.Cookie(name, value)
|
||||
return rb
|
||||
}
|
||||
|
||||
// Method sets the HTTP method for a request.
|
||||
// By default, requests without a body are GET,
|
||||
// and those with a body are POST.
|
||||
func (rb *Builder) Method(method string) *Builder {
|
||||
rb.rb.Method(method)
|
||||
return rb
|
||||
}
|
||||
|
||||
// Body sets the BodyGetter to use to build the body of a request.
|
||||
// The provided BodyGetter is used as an http.Request.GetBody func.
|
||||
// It implicitly sets method to POST.
|
||||
func (rb *Builder) Body(src BodyGetter) *Builder {
|
||||
rb.rb.Body(src)
|
||||
return rb
|
||||
}
|
||||
|
||||
// Client sets the http.Client to use for requests. If nil, it uses http.DefaultClient.
|
||||
func (rb *Builder) Client(cl *http.Client) *Builder {
|
||||
rb.cl = cl
|
||||
return rb
|
||||
}
|
||||
|
||||
// Transport sets the http.RoundTripper to use for requests.
|
||||
// If set, it makes a shallow copy of the http.Client before modifying it.
|
||||
func (rb *Builder) Transport(rt http.RoundTripper) *Builder {
|
||||
rb.rt = rt
|
||||
return rb
|
||||
}
|
||||
|
||||
// AddValidator adds a response validator to the Builder.
|
||||
// Adding a validator disables DefaultValidator.
|
||||
// To disable all validation, just add nil.
|
||||
func (rb *Builder) AddValidator(h ResponseHandler) *Builder {
|
||||
rb.validators = append(rb.validators, h)
|
||||
return rb
|
||||
}
|
||||
|
||||
// Handle sets the response handler for a Builder.
|
||||
// To use multiple handlers, use ChainHandlers.
|
||||
func (rb *Builder) Handle(h ResponseHandler) *Builder {
|
||||
rb.handler = h
|
||||
return rb
|
||||
}
|
||||
|
||||
// Config allows Builder to be extended by functions that set several options at once.
|
||||
func (rb *Builder) Config(cfgs ...Config) *Builder {
|
||||
for _, cfg := range cfgs {
|
||||
cfg(rb)
|
||||
}
|
||||
return rb
|
||||
}
|
||||
|
||||
// Clone creates a new Builder suitable for independent mutation.
|
||||
func (rb *Builder) Clone() *Builder {
|
||||
rb2 := *rb
|
||||
rb2.ub = *rb.ub.Clone()
|
||||
rb2.rb = *rb.rb.Clone()
|
||||
rb2.validators = slices.Clip(rb2.validators)
|
||||
return &rb2
|
||||
}
|
||||
|
||||
func joinerrs(a, b error) error {
|
||||
return fmt.Errorf("%w: %w", a, b)
|
||||
}
|
||||
|
||||
// URL builds a *url.URL from the base URL and options set on the Builder.
|
||||
// If a valid url.URL cannot be built,
|
||||
// URL() nevertheless returns a new url.URL,
|
||||
// so it is always safe to call u.String().
|
||||
func (rb *Builder) URL() (u *url.URL, err error) {
|
||||
u, err = rb.ub.URL()
|
||||
if err != nil {
|
||||
return u, joinerrs(ErrURL, err)
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// Request builds a new http.Request with its context set.
|
||||
func (rb *Builder) Request(ctx context.Context) (req *http.Request, err error) {
|
||||
u, err := rb.URL()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err = rb.rb.Request(ctx, u)
|
||||
if err != nil {
|
||||
return nil, joinerrs(ErrRequest, err)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// Do calls the underlying http.Client and validates and handles any resulting response. The response body is closed after all validators and the handler run.
|
||||
func (rb *Builder) Do(req *http.Request) (err error) {
|
||||
cl := cmp.Or(rb.cl, http.DefaultClient)
|
||||
if rb.rt != nil {
|
||||
cl2 := *cl
|
||||
cl2.Transport = rb.rt
|
||||
cl = &cl2
|
||||
}
|
||||
validators := rb.validators
|
||||
if len(validators) == 0 {
|
||||
validators = []ResponseHandler{DefaultValidator}
|
||||
}
|
||||
h := minitrue.Cond(rb.handler != nil,
|
||||
rb.handler,
|
||||
consumeBody)
|
||||
|
||||
code, err := do(cl, req, validators, h)
|
||||
switch code {
|
||||
case doOK:
|
||||
return nil
|
||||
case doConnect:
|
||||
err = joinerrs(ErrTransport, err)
|
||||
case doValidate:
|
||||
err = joinerrs(ErrValidator, err)
|
||||
case doHandle:
|
||||
err = joinerrs(ErrHandler, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Fetch builds a request, sends it, and handles the response.
|
||||
func (rb *Builder) Fetch(ctx context.Context) (err error) {
|
||||
req, err := rb.Request(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return rb.Do(req)
|
||||
}
|
||||
239
vendor/github.com/carlmjohnson/requests/builder_extras.go
generated
vendored
Normal file
239
vendor/github.com/carlmjohnson/requests/builder_extras.go
generated
vendored
Normal file
@@ -0,0 +1,239 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// URL creates a new Builder suitable for method chaining.
|
||||
// It is equivalent to calling BaseURL on an empty Builder.
|
||||
func URL(baseurl string) *Builder {
|
||||
return (&Builder{}).BaseURL(baseurl)
|
||||
}
|
||||
|
||||
// New creates a new Builder suitable for method chaining by applying the specified Configs.
|
||||
// It is equivalent to calling Config on an empty Builder.
|
||||
// The zero value of Builder is usable,
|
||||
// so it is not necessary to call New
|
||||
// when you do not have any Configs to apply.
|
||||
func New(cfgs ...Config) *Builder {
|
||||
return (&Builder{}).Config(cfgs...)
|
||||
}
|
||||
|
||||
// Head sets HTTP method to HEAD.
|
||||
func (rb *Builder) Head() *Builder {
|
||||
return rb.Method(http.MethodHead)
|
||||
}
|
||||
|
||||
// Post sets HTTP method to POST.
|
||||
//
|
||||
// Note that setting a Body causes a request to be POST by default.
|
||||
func (rb *Builder) Post() *Builder {
|
||||
return rb.Method(http.MethodPost)
|
||||
}
|
||||
|
||||
// Put sets HTTP method to PUT.
|
||||
func (rb *Builder) Put() *Builder {
|
||||
return rb.Method(http.MethodPut)
|
||||
}
|
||||
|
||||
// Patch sets HTTP method to PATCH.
|
||||
func (rb *Builder) Patch() *Builder {
|
||||
return rb.Method(http.MethodPatch)
|
||||
}
|
||||
|
||||
// Delete sets HTTP method to DELETE.
|
||||
func (rb *Builder) Delete() *Builder {
|
||||
return rb.Method(http.MethodDelete)
|
||||
}
|
||||
|
||||
// Hostf calls Host with fmt.Sprintf.
|
||||
func (rb *Builder) Hostf(format string, a ...any) *Builder {
|
||||
return rb.Host(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Pathf calls Path with fmt.Sprintf.
|
||||
//
|
||||
// Note that for security reasons, you must not use %s
|
||||
// with a user provided string!
|
||||
func (rb *Builder) Pathf(format string, a ...any) *Builder {
|
||||
return rb.Path(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// ParamInt converts value to a string and calls Param.
|
||||
func (rb *Builder) ParamInt(key string, value int) *Builder {
|
||||
return rb.Param(key, strconv.Itoa(value))
|
||||
}
|
||||
|
||||
// Params calls Param with all the members of m.
|
||||
func (rb *Builder) Params(m map[string][]string) *Builder {
|
||||
for k, vv := range m {
|
||||
rb.Param(k, vv...)
|
||||
}
|
||||
return rb
|
||||
}
|
||||
|
||||
// Headers calls Header with all the members of m.
|
||||
func (rb *Builder) Headers(m map[string][]string) *Builder {
|
||||
for k, vv := range m {
|
||||
rb.Header(k, vv...)
|
||||
}
|
||||
return rb
|
||||
}
|
||||
|
||||
// Accept sets the Accept header for a request.
|
||||
func (rb *Builder) Accept(contentTypes string) *Builder {
|
||||
return rb.Header("Accept", contentTypes)
|
||||
}
|
||||
|
||||
// CacheControl sets the client-side Cache-Control directive for a request.
|
||||
func (rb *Builder) CacheControl(directive string) *Builder {
|
||||
return rb.Header("Cache-Control", directive)
|
||||
}
|
||||
|
||||
// ContentType sets the Content-Type header on a request.
|
||||
func (rb *Builder) ContentType(ct string) *Builder {
|
||||
return rb.Header("Content-Type", ct)
|
||||
}
|
||||
|
||||
// UserAgent sets the User-Agent header.
|
||||
func (rb *Builder) UserAgent(s string) *Builder {
|
||||
return rb.Header("User-Agent", s)
|
||||
}
|
||||
|
||||
// BasicAuth sets the Authorization header to a basic auth credential.
|
||||
func (rb *Builder) BasicAuth(username, password string) *Builder {
|
||||
auth := username + ":" + password
|
||||
v := base64.StdEncoding.EncodeToString([]byte(auth))
|
||||
return rb.Header("Authorization", "Basic "+v)
|
||||
}
|
||||
|
||||
// Bearer sets the Authorization header to a bearer token.
|
||||
func (rb *Builder) Bearer(token string) *Builder {
|
||||
return rb.Header("Authorization", "Bearer "+token)
|
||||
}
|
||||
|
||||
// BodyReader sets the Builder's request body to r.
|
||||
func (rb *Builder) BodyReader(r io.Reader) *Builder {
|
||||
return rb.Body(BodyReader(r))
|
||||
}
|
||||
|
||||
// BodyWriter pipes writes from w to the Builder's request body.
|
||||
func (rb *Builder) BodyWriter(f func(w io.Writer) error) *Builder {
|
||||
return rb.Body(BodyWriter(f))
|
||||
}
|
||||
|
||||
// BodyBytes sets the Builder's request body to b.
|
||||
func (rb *Builder) BodyBytes(b []byte) *Builder {
|
||||
return rb.Body(BodyBytes(b))
|
||||
}
|
||||
|
||||
// BodySerializer sets the Builder's request body
|
||||
// to the serialized object.
|
||||
func (rb *Builder) BodySerializer(s Serializer, v any) *Builder {
|
||||
return rb.
|
||||
Body(BodySerializer(s, v))
|
||||
}
|
||||
|
||||
// BodyJSON sets the Builder's request body to the marshaled JSON.
|
||||
// It uses [JSONSerializer] to marshal the object.
|
||||
// It also sets ContentType to "application/json"
|
||||
// if it is not otherwise set.
|
||||
func (rb *Builder) BodyJSON(v any) *Builder {
|
||||
return rb.
|
||||
Body(BodyJSON(v)).
|
||||
HeaderOptional("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
// BodyForm sets the Builder's request body to the encoded form.
|
||||
// It also sets the ContentType to "application/x-www-form-urlencoded".
|
||||
func (rb *Builder) BodyForm(data url.Values) *Builder {
|
||||
return rb.
|
||||
Body(BodyForm(data)).
|
||||
ContentType("application/x-www-form-urlencoded")
|
||||
}
|
||||
|
||||
// BodyFile sets the Builder's request body to read from the given file path.
|
||||
func (rb *Builder) BodyFile(name string) *Builder {
|
||||
return rb.Body(BodyFile(name))
|
||||
}
|
||||
|
||||
// CheckStatus adds a validator for status code of a response.
|
||||
func (rb *Builder) CheckStatus(acceptStatuses ...int) *Builder {
|
||||
return rb.AddValidator(CheckStatus(acceptStatuses...))
|
||||
}
|
||||
|
||||
// CheckContentType adds a validator for the content type header of a response.
|
||||
func (rb *Builder) CheckContentType(cts ...string) *Builder {
|
||||
return rb.AddValidator(CheckContentType(cts...))
|
||||
}
|
||||
|
||||
// CheckPeek adds a validator that peeks at the first n bytes of a response body.
|
||||
func (rb *Builder) CheckPeek(n int, f func([]byte) error) *Builder {
|
||||
return rb.AddValidator(CheckPeek(n, f))
|
||||
}
|
||||
|
||||
// ToDeserializer sets the Builder to decode a response into v
|
||||
// using a [Deserializer].
|
||||
func (rb *Builder) ToDeserializer(d Deserializer, v any) *Builder {
|
||||
return rb.
|
||||
Handle(ToDeserializer(d, v))
|
||||
}
|
||||
|
||||
// ToJSON sets the Builder to decode a response as a JSON object.
|
||||
//
|
||||
// It uses [JSONDeserializer] to unmarshal the object.
|
||||
func (rb *Builder) ToJSON(v any) *Builder {
|
||||
return rb.Handle(ToJSON(v))
|
||||
}
|
||||
|
||||
// ToString sets the Builder to write the response body to the provided string pointer.
|
||||
func (rb *Builder) ToString(sp *string) *Builder {
|
||||
return rb.Handle(ToString(sp))
|
||||
}
|
||||
|
||||
// ToBytesBuffer sets the Builder to write the response body to the provided bytes.Buffer.
|
||||
func (rb *Builder) ToBytesBuffer(buf *bytes.Buffer) *Builder {
|
||||
return rb.Handle(ToBytesBuffer(buf))
|
||||
}
|
||||
|
||||
// ToWriter sets the Builder to copy the response body into w.
|
||||
func (rb *Builder) ToWriter(w io.Writer) *Builder {
|
||||
return rb.Handle(ToWriter(w))
|
||||
}
|
||||
|
||||
// ToFile sets the Builder to write the response body to the given file name.
|
||||
// The file and its parent directories are created automatically.
|
||||
// For more advanced use cases, use ToWriter.
|
||||
func (rb *Builder) ToFile(name string) *Builder {
|
||||
return rb.Handle(ToFile(name))
|
||||
}
|
||||
|
||||
// CopyHeaders adds a validator which copies the response headers to h.
|
||||
// Note that because CopyHeaders adds a validator,
|
||||
// the DefaultValidator is disabled and must be added back manually
|
||||
// if status code validation is desired.
|
||||
func (rb *Builder) CopyHeaders(h map[string][]string) *Builder {
|
||||
return rb.
|
||||
AddValidator(CopyHeaders(h))
|
||||
}
|
||||
|
||||
// ToHeaders sets the method to HEAD and adds a handler which copies the response headers to h.
|
||||
// To just copy headers, see Builder.CopyHeaders.
|
||||
func (rb *Builder) ToHeaders(h map[string][]string) *Builder {
|
||||
return rb.
|
||||
Head().
|
||||
Handle(ChainHandlers(CopyHeaders(h), consumeBody))
|
||||
}
|
||||
|
||||
// ErrorJSON adds a validator that applies DefaultValidator
|
||||
// and decodes the response as a JSON object
|
||||
// if the DefaultValidator check fails.
|
||||
func (rb *Builder) ErrorJSON(v any) *Builder {
|
||||
return rb.AddValidator(ErrorJSON(v))
|
||||
}
|
||||
77
vendor/github.com/carlmjohnson/requests/config.go
generated
vendored
Normal file
77
vendor/github.com/carlmjohnson/requests/config.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http/httptest"
|
||||
)
|
||||
|
||||
// Config allows Builder to be extended by setting several options at once.
|
||||
// For example, a Config might set a Body and its ContentType.
|
||||
type Config = func(rb *Builder)
|
||||
|
||||
// GzipConfig writes a gzip stream to its request body using a callback.
|
||||
// It also sets the appropriate Content-Encoding header and automatically
|
||||
// closes and the stream when the callback returns.
|
||||
func GzipConfig(level int, h func(gw *gzip.Writer) error) Config {
|
||||
return func(rb *Builder) {
|
||||
rb.
|
||||
Header("Content-Encoding", "gzip").
|
||||
BodyWriter(func(w io.Writer) error {
|
||||
gw, err := gzip.NewWriterLevel(w, level)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = h(gw); err != nil {
|
||||
gw.Close()
|
||||
return err
|
||||
}
|
||||
return gw.Close()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestServerConfig returns a Config
|
||||
// which sets the Builder's BaseURL to s.URL
|
||||
// and the Builder's Client to s.Client().
|
||||
//
|
||||
// Deprecated: Use reqtest.Server.
|
||||
func TestServerConfig(s *httptest.Server) Config {
|
||||
return func(rb *Builder) {
|
||||
rb.
|
||||
BaseURL(s.URL).
|
||||
Client(s.Client())
|
||||
}
|
||||
}
|
||||
|
||||
// BodyMultipart returns a Config
|
||||
// that uses a multipart.Writer for the request body.
|
||||
// If boundary is "", a multipart boundary is chosen at random.
|
||||
// The content type of the request is set to multipart/form-data
|
||||
// with the correct boundary.
|
||||
// The multipart.Writer is automatically closed if the callback succeeds.
|
||||
func BodyMultipart(boundary string, h func(multi *multipart.Writer) error) Config {
|
||||
return func(rb *Builder) {
|
||||
if boundary == "" {
|
||||
multi := multipart.NewWriter(nil)
|
||||
boundary = multi.Boundary()
|
||||
}
|
||||
rb.
|
||||
ContentType("multipart/form-data; boundary=" + boundary).
|
||||
BodyWriter(func(w io.Writer) error {
|
||||
multi := multipart.NewWriter(w)
|
||||
if err := multi.SetBoundary(boundary); err != nil {
|
||||
return fmt.Errorf("setting boundary: %w", err)
|
||||
}
|
||||
if err := h(multi); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := multi.Close(); err != nil {
|
||||
return fmt.Errorf("closing multipart writer: %w", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
18
vendor/github.com/carlmjohnson/requests/cookies.go
generated
vendored
Normal file
18
vendor/github.com/carlmjohnson/requests/cookies.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
// NewCookieJar returns a cookie jar using the standard public suffix list.
|
||||
func NewCookieJar() http.CookieJar {
|
||||
jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
|
||||
// As of Go 1.16, cookiejar.New err is hardcoded nil
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return jar
|
||||
}
|
||||
36
vendor/github.com/carlmjohnson/requests/core_do.go
generated
vendored
Normal file
36
vendor/github.com/carlmjohnson/requests/core_do.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type doResponse int
|
||||
|
||||
const (
|
||||
doOK doResponse = iota
|
||||
doConnect
|
||||
doValidate
|
||||
doHandle
|
||||
)
|
||||
|
||||
func do(cl *http.Client, req *http.Request, validators []ResponseHandler, h ResponseHandler) (doResponse, error) {
|
||||
res, err := cl.Do(req)
|
||||
if err != nil {
|
||||
return doConnect, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
for _, v := range validators {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
if err = v(res); err != nil {
|
||||
return doValidate, err
|
||||
}
|
||||
}
|
||||
if err = h(res); err != nil {
|
||||
return doHandle, err
|
||||
}
|
||||
|
||||
return doOK, nil
|
||||
}
|
||||
106
vendor/github.com/carlmjohnson/requests/core_req.go
generated
vendored
Normal file
106
vendor/github.com/carlmjohnson/requests/core_req.go
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
|
||||
"github.com/carlmjohnson/requests/internal/minitrue"
|
||||
)
|
||||
|
||||
// nopCloser is like io.NopCloser(),
|
||||
// but it is a concrete type so we can strip it out
|
||||
// before setting a body on a request.
|
||||
// See https://github.com/carlmjohnson/requests/discussions/49
|
||||
type nopCloser struct {
|
||||
io.Reader
|
||||
}
|
||||
|
||||
func rc(r io.Reader) nopCloser {
|
||||
return nopCloser{r}
|
||||
}
|
||||
|
||||
func (nopCloser) Close() error { return nil }
|
||||
|
||||
var _ io.ReadCloser = nopCloser{}
|
||||
|
||||
type requestBuilder struct {
|
||||
headers []multimap
|
||||
cookies []kvpair
|
||||
getBody BodyGetter
|
||||
method string
|
||||
}
|
||||
|
||||
func (rb *requestBuilder) Header(key string, values ...string) {
|
||||
rb.headers = append(rb.headers, multimap{key, values, false})
|
||||
}
|
||||
|
||||
func (rb *requestBuilder) HeaderOptional(key string, values ...string) {
|
||||
rb.headers = append(rb.headers, multimap{key, values, true})
|
||||
}
|
||||
|
||||
func (rb *requestBuilder) Cookie(name, value string) {
|
||||
rb.cookies = append(rb.cookies, kvpair{name, value})
|
||||
}
|
||||
|
||||
func (rb *requestBuilder) Method(method string) {
|
||||
rb.method = method
|
||||
}
|
||||
|
||||
func (rb *requestBuilder) Body(src BodyGetter) {
|
||||
rb.getBody = src
|
||||
}
|
||||
|
||||
// Clone creates a new Builder suitable for independent mutation.
|
||||
func (rb *requestBuilder) Clone() *requestBuilder {
|
||||
rb2 := *rb
|
||||
rb2.headers = slices.Clip(rb2.headers)
|
||||
rb2.cookies = slices.Clip(rb2.cookies)
|
||||
return &rb2
|
||||
}
|
||||
|
||||
// Request builds a new http.Request with its context set.
|
||||
func (rb *requestBuilder) Request(ctx context.Context, u *url.URL) (req *http.Request, err error) {
|
||||
var body io.Reader
|
||||
if rb.getBody != nil {
|
||||
if body, err = rb.getBody(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if nopper, ok := body.(nopCloser); ok {
|
||||
body = nopper.Reader
|
||||
}
|
||||
}
|
||||
method := cmp.Or(rb.method,
|
||||
minitrue.Cond(rb.getBody == nil,
|
||||
http.MethodGet,
|
||||
http.MethodPost))
|
||||
|
||||
req, err = http.NewRequestWithContext(ctx, method, u.String(), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.GetBody = rb.getBody
|
||||
|
||||
for _, kv := range rb.headers {
|
||||
if !kv.optional {
|
||||
req.Header[http.CanonicalHeaderKey(kv.key)] = kv.values
|
||||
}
|
||||
}
|
||||
for _, kv := range rb.headers {
|
||||
if kv.optional &&
|
||||
req.Header.Get(kv.key) == "" &&
|
||||
cmp.Or(kv.values...) != "" {
|
||||
req.Header[http.CanonicalHeaderKey(kv.key)] = kv.values
|
||||
}
|
||||
}
|
||||
for _, kv := range rb.cookies {
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: kv.key,
|
||||
Value: kv.value,
|
||||
})
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
94
vendor/github.com/carlmjohnson/requests/core_url.go
generated
vendored
Normal file
94
vendor/github.com/carlmjohnson/requests/core_url.go
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"net/url"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type multimap struct {
|
||||
key string
|
||||
values []string
|
||||
optional bool
|
||||
}
|
||||
|
||||
type kvpair struct {
|
||||
key, value string
|
||||
}
|
||||
|
||||
type urlBuilder struct {
|
||||
baseurl string
|
||||
scheme, host string
|
||||
paths []string
|
||||
params []multimap
|
||||
}
|
||||
|
||||
func (ub *urlBuilder) BaseURL(baseurl string) {
|
||||
ub.baseurl = baseurl
|
||||
}
|
||||
|
||||
func (ub *urlBuilder) Scheme(scheme string) {
|
||||
ub.scheme = scheme
|
||||
}
|
||||
|
||||
func (ub *urlBuilder) Host(host string) {
|
||||
ub.host = host
|
||||
|
||||
}
|
||||
|
||||
func (ub *urlBuilder) Path(path string) {
|
||||
ub.paths = append(ub.paths, path)
|
||||
}
|
||||
|
||||
func (ub *urlBuilder) Param(key string, values ...string) {
|
||||
ub.params = append(ub.params, multimap{key, values, false})
|
||||
}
|
||||
|
||||
func (ub *urlBuilder) ParamOptional(key string, values ...string) {
|
||||
ub.params = append(ub.params, multimap{key, values, true})
|
||||
}
|
||||
|
||||
func (ub *urlBuilder) Clone() *urlBuilder {
|
||||
ub2 := *ub
|
||||
ub2.paths = slices.Clip(ub2.paths)
|
||||
ub2.params = slices.Clip(ub2.params)
|
||||
return &ub2
|
||||
}
|
||||
|
||||
func (ub *urlBuilder) URL() (u *url.URL, err error) {
|
||||
u, err = url.Parse(ub.baseurl)
|
||||
if err != nil {
|
||||
return new(url.URL), err
|
||||
}
|
||||
u.Scheme = cmp.Or(
|
||||
ub.scheme,
|
||||
u.Scheme,
|
||||
"https",
|
||||
)
|
||||
u.Host = cmp.Or(ub.host, u.Host)
|
||||
for _, p := range ub.paths {
|
||||
u.Path = u.ResolveReference(&url.URL{Path: p}).Path
|
||||
}
|
||||
if len(ub.params) > 0 {
|
||||
q := u.Query()
|
||||
for _, kv := range ub.params {
|
||||
if !kv.optional {
|
||||
q[kv.key] = kv.values
|
||||
}
|
||||
}
|
||||
for _, kv := range ub.params {
|
||||
if kv.optional &&
|
||||
q.Get(kv.key) == "" &&
|
||||
cmp.Or(kv.values...) != "" {
|
||||
q[kv.key] = kv.values
|
||||
}
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
}
|
||||
// Reparsing, in case the path rewriting broke the URL
|
||||
u, err = url.Parse(u.String())
|
||||
if err != nil {
|
||||
return new(url.URL), err
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
3
vendor/github.com/carlmjohnson/requests/doc.go
generated
vendored
Normal file
3
vendor/github.com/carlmjohnson/requests/doc.go
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
// Package requests is a convenience wrapper around net/http to make it faster
|
||||
// and easier to build requests and custom transports.
|
||||
package requests
|
||||
20
vendor/github.com/carlmjohnson/requests/errorkind.go
generated
vendored
Normal file
20
vendor/github.com/carlmjohnson/requests/errorkind.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
package requests
|
||||
|
||||
// ErrorKind indicates where an error was returned in the process of building, validating, and handling a request.
|
||||
// Errors returned by Builder can be tested for their ErrorKind using errors.Is or errors.As.
|
||||
type ErrorKind int8
|
||||
|
||||
//go:generate stringer -type=ErrorKind
|
||||
|
||||
// Enum values for type ErrorKind
|
||||
const (
|
||||
ErrURL ErrorKind = iota // error building URL
|
||||
ErrRequest // error building the request
|
||||
ErrTransport // error connecting
|
||||
ErrValidator // validator error
|
||||
ErrHandler // handler error
|
||||
)
|
||||
|
||||
func (ek ErrorKind) Error() string {
|
||||
return ek.String()
|
||||
}
|
||||
27
vendor/github.com/carlmjohnson/requests/errorkind_string.go
generated
vendored
Normal file
27
vendor/github.com/carlmjohnson/requests/errorkind_string.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
// Code generated by "stringer -type=ErrorKind"; DO NOT EDIT.
|
||||
|
||||
package requests
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[ErrURL-0]
|
||||
_ = x[ErrRequest-1]
|
||||
_ = x[ErrTransport-2]
|
||||
_ = x[ErrValidator-3]
|
||||
_ = x[ErrHandler-4]
|
||||
}
|
||||
|
||||
const _ErrorKind_name = "ErrURLErrRequestErrTransportErrValidatorErrHandler"
|
||||
|
||||
var _ErrorKind_index = [...]uint8{0, 6, 16, 28, 40, 50}
|
||||
|
||||
func (i ErrorKind) String() string {
|
||||
if i < 0 || i >= ErrorKind(len(_ErrorKind_index)-1) {
|
||||
return "ErrorKind(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _ErrorKind_name[_ErrorKind_index[i]:_ErrorKind_index[i+1]]
|
||||
}
|
||||
139
vendor/github.com/carlmjohnson/requests/handler.go
generated
vendored
Normal file
139
vendor/github.com/carlmjohnson/requests/handler.go
generated
vendored
Normal file
@@ -0,0 +1,139 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
// ResponseHandler is used to validate or handle the response to a request.
|
||||
type ResponseHandler = func(*http.Response) error
|
||||
|
||||
// ChainHandlers allows for the composing of validators or response handlers.
|
||||
func ChainHandlers(handlers ...ResponseHandler) ResponseHandler {
|
||||
return func(r *http.Response) error {
|
||||
for _, h := range handlers {
|
||||
if h == nil {
|
||||
continue
|
||||
}
|
||||
if err := h(r); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func consumeBody(res *http.Response) (err error) {
|
||||
const maxDiscardSize = 640 * 1 << 10
|
||||
if _, err = io.CopyN(io.Discard, res.Body, maxDiscardSize); err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ToDeserializer decodes a response into v using a [Deserializer].
|
||||
func ToDeserializer(d Deserializer, v any) ResponseHandler {
|
||||
return func(res *http.Response) error {
|
||||
data, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = d(data, v); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ToJSON decodes a response as a JSON object.
|
||||
//
|
||||
// It uses [JSONDeserializer] to unmarshal the object.
|
||||
func ToJSON(v any) ResponseHandler {
|
||||
return ToDeserializer(JSONDeserializer, v)
|
||||
}
|
||||
|
||||
// ToString writes the response body to the provided string pointer.
|
||||
func ToString(sp *string) ResponseHandler {
|
||||
return func(res *http.Response) error {
|
||||
var buf strings.Builder
|
||||
_, err := io.Copy(&buf, res.Body)
|
||||
if err == nil {
|
||||
*sp = buf.String()
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// ToBytesBuffer writes the response body to the provided bytes.Buffer.
|
||||
func ToBytesBuffer(buf *bytes.Buffer) ResponseHandler {
|
||||
return func(res *http.Response) error {
|
||||
_, err := io.Copy(buf, res.Body)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// ToBufioReader takes a callback which wraps the response body in a bufio.Reader.
|
||||
func ToBufioReader(f func(r *bufio.Reader) error) ResponseHandler {
|
||||
return func(res *http.Response) error {
|
||||
return f(bufio.NewReader(res.Body))
|
||||
}
|
||||
}
|
||||
|
||||
// ToBufioScanner takes a callback which wraps the response body in a bufio.Scanner.
|
||||
func ToBufioScanner(f func(r *bufio.Scanner) error) ResponseHandler {
|
||||
return func(res *http.Response) error {
|
||||
return f(bufio.NewScanner(res.Body))
|
||||
}
|
||||
}
|
||||
|
||||
// ToHTML parses the page with x/net/html.Parse.
|
||||
//
|
||||
// Deprecated: Use reqhtml.To.
|
||||
func ToHTML(n *html.Node) ResponseHandler {
|
||||
return ToBufioReader(func(r *bufio.Reader) error {
|
||||
n2, err := html.Parse(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*n = *n2
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ToWriter copies the response body to w.
|
||||
func ToWriter(w io.Writer) ResponseHandler {
|
||||
return ToBufioReader(func(r *bufio.Reader) error {
|
||||
_, err := io.Copy(w, r)
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// ToFile writes the response body at the provided file path.
|
||||
// The file and its parent directories are created automatically.
|
||||
func ToFile(name string) ResponseHandler {
|
||||
return func(res *http.Response) error {
|
||||
_ = os.MkdirAll(filepath.Dir(name), 0777)
|
||||
|
||||
f, err := os.Create(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = io.Copy(f, res.Body)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// ToHeaders is an alias for backwards compatibility.
|
||||
//
|
||||
// Deprecated: Use CopyHeaders
|
||||
var ToHeaders = CopyHeaders
|
||||
9
vendor/github.com/carlmjohnson/requests/internal/minitrue/minitrue.go
generated
vendored
Normal file
9
vendor/github.com/carlmjohnson/requests/internal/minitrue/minitrue.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// Package minitrue - Whatever the Package holds to be the truth, is truth.
|
||||
package minitrue
|
||||
|
||||
func Cond[T any](val bool, a, b T) T {
|
||||
if val {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
131
vendor/github.com/carlmjohnson/requests/recorder.go
generated
vendored
Normal file
131
vendor/github.com/carlmjohnson/requests/recorder.go
generated
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Record returns an http.RoundTripper that writes out its
|
||||
// requests and their responses to text files in basepath.
|
||||
// Requests are named according to a hash of their contents.
|
||||
// Responses are named according to the request that made them.
|
||||
//
|
||||
// Deprecated: Use reqtest.Record.
|
||||
func Record(rt http.RoundTripper, basepath string) Transport {
|
||||
if rt == nil {
|
||||
rt = http.DefaultTransport
|
||||
}
|
||||
return RoundTripFunc(func(req *http.Request) (res *http.Response, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("problem while recording transport: %w", err)
|
||||
}
|
||||
}()
|
||||
_ = os.MkdirAll(basepath, 0755)
|
||||
b, err := httputil.DumpRequest(req, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqname, resname := buildName(b)
|
||||
name := filepath.Join(basepath, reqname)
|
||||
if err = os.WriteFile(name, b, 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res, err = rt.RoundTrip(req); err != nil {
|
||||
return
|
||||
}
|
||||
b, err = httputil.DumpResponse(res, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name = filepath.Join(basepath, resname)
|
||||
if err = os.WriteFile(name, b, 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// Replay returns an http.RoundTripper that reads its
|
||||
// responses from text files in basepath.
|
||||
// Responses are looked up according to a hash of the request.
|
||||
//
|
||||
// Deprecated: Use reqtest.Replay.
|
||||
func Replay(basepath string) Transport {
|
||||
return ReplayFS(os.DirFS(basepath))
|
||||
}
|
||||
|
||||
var errNotFound = errors.New("response not found")
|
||||
|
||||
// ReplayFS returns an http.RoundTripper that reads its
|
||||
// responses from text files in the fs.FS.
|
||||
// Responses are looked up according to a hash of the request.
|
||||
// Response file names may optionally be prefixed with comments for better human organization.
|
||||
//
|
||||
// Deprecated: Use reqtest.ReplayFS.
|
||||
func ReplayFS(fsys fs.FS) Transport {
|
||||
return RoundTripFunc(func(req *http.Request) (res *http.Response, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("problem while replaying transport: %w", err)
|
||||
}
|
||||
}()
|
||||
b, err := httputil.DumpRequest(req, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, name := buildName(b)
|
||||
glob := "*" + name
|
||||
matches, err := fs.Glob(fsys, glob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(matches) == 0 {
|
||||
return nil, fmt.Errorf("%w: no replay file matches %q", errNotFound, glob)
|
||||
}
|
||||
if len(matches) > 1 {
|
||||
return nil, fmt.Errorf("ambiguous response: multiple replay files match %q", glob)
|
||||
}
|
||||
b, err = fs.ReadFile(fsys, matches[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := bufio.NewReader(bytes.NewReader(b))
|
||||
return http.ReadResponse(r, req)
|
||||
})
|
||||
}
|
||||
|
||||
func buildName(b []byte) (reqname, resname string) {
|
||||
h := md5.New()
|
||||
h.Write(b)
|
||||
s := base64.URLEncoding.EncodeToString(h.Sum(nil))
|
||||
return s[:8] + ".req.txt", s[:8] + ".res.txt"
|
||||
}
|
||||
|
||||
// Caching returns an http.RoundTripper that attempts to read its
|
||||
// responses from text files in basepath. If the response is absent,
|
||||
// it caches the result of issuing the request with rt in basepath.
|
||||
// Requests are named according to a hash of their contents.
|
||||
// Responses are named according to the request that made them.
|
||||
//
|
||||
// Deprecated: Use reqtest.Caching.
|
||||
func Caching(rt http.RoundTripper, basepath string) Transport {
|
||||
replay := Replay(basepath).RoundTrip
|
||||
record := Record(rt, basepath).RoundTrip
|
||||
return RoundTripFunc(func(req *http.Request) (res *http.Response, err error) {
|
||||
res, err = replay(req)
|
||||
if errors.Is(err, errNotFound) {
|
||||
res, err = record(req)
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
19
vendor/github.com/carlmjohnson/requests/redirects.go
generated
vendored
Normal file
19
vendor/github.com/carlmjohnson/requests/redirects.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
package requests
|
||||
|
||||
import "net/http"
|
||||
|
||||
// CheckRedirectPolicy is a function suitable for use as CheckRedirect on an http.Client.
|
||||
type CheckRedirectPolicy = func(req *http.Request, via []*http.Request) error
|
||||
|
||||
// MaxFollow returns a CheckRedirectPolicy that follows a maximum of n redirects.
|
||||
func MaxFollow(n int) CheckRedirectPolicy {
|
||||
return func(req *http.Request, via []*http.Request) error {
|
||||
if len(via) > n {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NoFollow is a CheckRedirectPolicy that does not follow redirects.
|
||||
var NoFollow CheckRedirectPolicy = MaxFollow(0)
|
||||
24
vendor/github.com/carlmjohnson/requests/serializer.go
generated
vendored
Normal file
24
vendor/github.com/carlmjohnson/requests/serializer.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// Serializer is a function
|
||||
// that can convert arbitrary data
|
||||
// to bytes in some format.
|
||||
type Serializer = func(v any) ([]byte, error)
|
||||
|
||||
// Deserializer is a function
|
||||
// that can read data in some format
|
||||
// and store the result in v.
|
||||
type Deserializer = func(data []byte, v any) error
|
||||
|
||||
var (
|
||||
// JSONSerializer is used by BodyJSON and Builder.BodyJSON.
|
||||
// The default serializer may be changed in a future version of requests.
|
||||
JSONSerializer Serializer = json.Marshal
|
||||
// JSONDeserializer is used by ToJSON and Builder.ToJSON.
|
||||
// The default deserializer may be changed in a future version of requests.
|
||||
JSONDeserializer Deserializer = json.Unmarshal
|
||||
)
|
||||
128
vendor/github.com/carlmjohnson/requests/transport.go
generated
vendored
Normal file
128
vendor/github.com/carlmjohnson/requests/transport.go
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Transport is an alias of http.RoundTripper for documentation purposes.
|
||||
type Transport = http.RoundTripper
|
||||
|
||||
// RoundTripFunc is an adaptor to use a function as an http.RoundTripper.
|
||||
type RoundTripFunc func(req *http.Request) (res *http.Response, err error)
|
||||
|
||||
// RoundTrip implements http.RoundTripper.
|
||||
func (rtf RoundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
return rtf(r)
|
||||
}
|
||||
|
||||
var _ Transport = RoundTripFunc(nil)
|
||||
|
||||
// ReplayString returns an http.RoundTripper that always responds with a
|
||||
// request built from rawResponse. It is intended for use in one-off tests.
|
||||
//
|
||||
// Deprecated: Use reqtest.ReplayString.
|
||||
func ReplayString(rawResponse string) Transport {
|
||||
return RoundTripFunc(func(req *http.Request) (res *http.Response, err error) {
|
||||
r := bufio.NewReader(strings.NewReader(rawResponse))
|
||||
res, err = http.ReadResponse(r, req)
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// UserAgentTransport returns a wrapped http.RoundTripper that sets the User-Agent header on requests to s.
|
||||
func UserAgentTransport(rt http.RoundTripper, s string) Transport {
|
||||
if rt == nil {
|
||||
rt = http.DefaultTransport
|
||||
}
|
||||
return RoundTripFunc(func(req *http.Request) (res *http.Response, err error) {
|
||||
r2 := *req
|
||||
r2.Header = r2.Header.Clone()
|
||||
r2.Header.Set("User-Agent", s)
|
||||
return rt.RoundTrip(&r2)
|
||||
})
|
||||
}
|
||||
|
||||
// PermitURLTransport returns a wrapped http.RoundTripper that rejects any requests whose URL doesn't match the provided regular expression string.
|
||||
//
|
||||
// PermitURLTransport will panic if the regexp does not compile.
|
||||
func PermitURLTransport(rt http.RoundTripper, regex string) Transport {
|
||||
if rt == nil {
|
||||
rt = http.DefaultTransport
|
||||
}
|
||||
re := regexp.MustCompile(regex)
|
||||
reErr := fmt.Errorf("requested URL not permitted by regexp: %s", regex)
|
||||
return RoundTripFunc(func(req *http.Request) (res *http.Response, err error) {
|
||||
if u := req.URL.String(); !re.MatchString(u) {
|
||||
return nil, reErr
|
||||
}
|
||||
return rt.RoundTrip(req)
|
||||
})
|
||||
}
|
||||
|
||||
// LogTransport returns a wrapped http.RoundTripper
|
||||
// that calls fn with details when a response has finished.
|
||||
// A response is considered finished
|
||||
// when the wrapper http.RoundTripper returns an error
|
||||
// or the Response.Body is closed,
|
||||
// whichever comes first.
|
||||
// To simplify logging code,
|
||||
// a nil *http.Response is replaced with a new http.Response.
|
||||
func LogTransport(rt http.RoundTripper, fn func(req *http.Request, res *http.Response, err error, duration time.Duration)) Transport {
|
||||
if rt == nil {
|
||||
rt = http.DefaultTransport
|
||||
}
|
||||
return RoundTripFunc(func(req *http.Request) (res *http.Response, err error) {
|
||||
start := time.Now()
|
||||
res, err = rt.RoundTrip(req)
|
||||
if err != nil {
|
||||
res2 := res
|
||||
if res == nil {
|
||||
res2 = new(http.Response)
|
||||
}
|
||||
fn(req, res2, err, time.Since(start))
|
||||
return
|
||||
}
|
||||
|
||||
res.Body = closeLogger{res.Body, func() {
|
||||
fn(req, res, err, time.Since(start))
|
||||
}}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
type closeLogger struct {
|
||||
io.ReadCloser
|
||||
fn func()
|
||||
}
|
||||
|
||||
func (cl closeLogger) Close() error {
|
||||
cl.fn()
|
||||
return cl.ReadCloser.Close()
|
||||
}
|
||||
|
||||
// DoerTransport converts a Doer into a Transport.
|
||||
// It exists for compatibility with other libraries.
|
||||
// A Doer is an interface with a Do method.
|
||||
// Users should prefer Transport,
|
||||
// because Do is the interface of http.Client
|
||||
// which has higher level concerns.
|
||||
func DoerTransport(cl interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
}) Transport {
|
||||
return RoundTripFunc(cl.Do)
|
||||
}
|
||||
|
||||
// ErrorTransport always returns the specified error instead of connecting.
|
||||
// It is intended for use in testing
|
||||
// or to prevent accidental use of http.DefaultClient.
|
||||
func ErrorTransport(err error) Transport {
|
||||
return RoundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
return nil, err
|
||||
})
|
||||
}
|
||||
102
vendor/github.com/carlmjohnson/requests/validator.go
generated
vendored
Normal file
102
vendor/github.com/carlmjohnson/requests/validator.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"mime"
|
||||
"net/http"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// DefaultValidator is the validator applied by Builder unless otherwise specified.
|
||||
var DefaultValidator ResponseHandler = CheckStatus(
|
||||
http.StatusOK,
|
||||
http.StatusCreated,
|
||||
http.StatusAccepted,
|
||||
http.StatusNonAuthoritativeInfo,
|
||||
http.StatusNoContent,
|
||||
)
|
||||
|
||||
// ResponseError is the error type produced by CheckStatus and CheckContentType.
|
||||
type ResponseError http.Response
|
||||
|
||||
// Error fulfills the error interface.
|
||||
func (se *ResponseError) Error() string {
|
||||
return fmt.Sprintf("response error for %s", se.Request.URL.Redacted())
|
||||
}
|
||||
|
||||
// CheckStatus validates the response has an acceptable status code.
|
||||
func CheckStatus(acceptStatuses ...int) ResponseHandler {
|
||||
return func(res *http.Response) error {
|
||||
if slices.Contains(acceptStatuses, res.StatusCode) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("%w: unexpected status: %d",
|
||||
(*ResponseError)(res), res.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// HasStatusErr returns true if err is a ResponseError caused by any of the codes given.
|
||||
func HasStatusErr(err error, codes ...int) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
if se := new(ResponseError); errors.As(err, &se) {
|
||||
return slices.Contains(codes, se.StatusCode)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// CheckContentType validates that a response has one of the given content type headers.
|
||||
func CheckContentType(cts ...string) ResponseHandler {
|
||||
return func(res *http.Response) error {
|
||||
mt, _, err := mime.ParseMediaType(res.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: problem matching Content-Type",
|
||||
(*ResponseError)(res))
|
||||
}
|
||||
if slices.Contains(cts, mt) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%w: unexpected Content-Type: %s",
|
||||
(*ResponseError)(res), mt)
|
||||
}
|
||||
}
|
||||
|
||||
type bufioCloser struct {
|
||||
*bufio.Reader
|
||||
io.Closer
|
||||
}
|
||||
|
||||
// CheckPeek wraps the body of a response in a bufio.Reader and
|
||||
// gives f a peek at the first n bytes for validation.
|
||||
func CheckPeek(n int, f func([]byte) error) ResponseHandler {
|
||||
return func(res *http.Response) error {
|
||||
// ensure buffer is at least minimum size
|
||||
buf := bufio.NewReader(res.Body)
|
||||
// ensure large peeks will fit in the buffer
|
||||
buf = bufio.NewReaderSize(buf, n)
|
||||
res.Body = &bufioCloser{
|
||||
buf,
|
||||
res.Body,
|
||||
}
|
||||
b, err := buf.Peek(n)
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
return f(b)
|
||||
}
|
||||
}
|
||||
|
||||
// CopyHeaders copies the response headers to h.
|
||||
func CopyHeaders(h map[string][]string) ResponseHandler {
|
||||
return func(res *http.Response) error {
|
||||
maps.Copy(h, res.Header)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
34
vendor/github.com/carlmjohnson/requests/validator_handler.go
generated
vendored
Normal file
34
vendor/github.com/carlmjohnson/requests/validator_handler.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// ValidatorHandler composes a Validator and a Handler.
|
||||
// If the validation check fails, it triggers the handler.
|
||||
// Any errors from validator or handler will be joined to the error returned.
|
||||
// If the handler succeeds, the error will matching ErrInvalidHandled.
|
||||
func ValidatorHandler(v, h ResponseHandler) ResponseHandler {
|
||||
return func(res *http.Response) error {
|
||||
err1 := v(res)
|
||||
if err1 == nil { // passes validation
|
||||
return nil
|
||||
}
|
||||
err2 := h(res)
|
||||
if err2 == nil { // successfully handled
|
||||
return fmt.Errorf("%w: %w", ErrInvalidHandled, err1)
|
||||
}
|
||||
return errors.Join(err1, err2)
|
||||
}
|
||||
}
|
||||
|
||||
var ErrInvalidHandled = errors.New("handled recovery from invalid response")
|
||||
|
||||
// ErrorJSON is a ValidatorHandler that applies DefaultValidator
|
||||
// and decodes the response as a JSON object
|
||||
// if the DefaultValidator check fails.
|
||||
func ErrorJSON(v any) ResponseHandler {
|
||||
return ValidatorHandler(DefaultValidator, ToJSON(v))
|
||||
}
|
||||
Reference in New Issue
Block a user